[{"data":1,"prerenderedAt":729},["ShallowReactive",2],{"post-fr-nuxt4-introduction":3},{"id":4,"title":5,"body":6,"date":715,"description":17,"excerpt":716,"extension":717,"meta":718,"navigation":452,"path":719,"readTime":353,"seo":720,"slug":721,"stem":722,"tags":723,"__hash__":728},"fr_blog/fr/blog/nuxt4-introduction.md","Nuxt 4 : ce que ça change concrètement pour les développeurs",{"type":7,"value":8,"toc":704},"minimark",[9,14,18,23,26,29,37,40,50,56,62,72,86,97,100,110,216,219,223,229,357,371,375,386,494,497,501,504,604,609,683,687,690,700],[10,11,13],"h1",{"id":12},"nuxt-4-ce-que-ça-change-concrètement","Nuxt 4 : ce que ça change concrètement",[15,16,17],"p",{},"Si tu travailles avec Vue.js et que tu n'as pas encore regardé Nuxt 4, c'est le bon moment. Ce n'est pas une révolution, mais les changements structurels sont suffisamment significatifs pour mériter un tour d'horizon avant de démarrer un nouveau projet.",[19,20,22],"h2",{"id":21},"cest-quoi-nuxt-rapidement","C'est quoi Nuxt, rapidement",[15,24,25],{},"Nuxt est un meta-framework construit sur Vue.js. Il gère pour toi le routing, le rendu côté serveur (SSR), la génération statique (SSG), le fetching de données, et bien d'autres choses. En gros : tu codes des composants Vue, Nuxt s'occupe du reste.",[15,27,28],{},"La version 3 a introduit la Composition API de Vue 3, Nitro comme moteur serveur, et une architecture basée sur les auto-imports. Nuxt 4 affine tout ça — et introduit quelques changements de fond.",[19,30,32,33],{"id":31},"le-changement-principal-le-répertoire-app","Le changement principal : le répertoire ",[34,35,36],"code",{},"app/",[15,38,39],{},"En Nuxt 3, la structure d'un projet ressemble à ça :",[41,42,47],"pre",{"className":43,"code":45,"language":46},[44],"language-text","├── components/\n├── composables/\n├── layouts/\n├── middleware/\n├── pages/\n├── plugins/\n├── server/\n├── nuxt.config.ts\n","text",[34,48,45],{"__ignoreMap":49},"",[15,51,52,53,55],{},"En Nuxt 4, tout le code applicatif est regroupé sous un répertoire ",[34,54,36],{}," :",[41,57,60],{"className":58,"code":59,"language":46},[44],"├── app/\n│   ├── components/\n│   ├── composables/\n│   ├── layouts/\n│   ├── middleware/\n│   ├── pages/\n│   ├── plugins/\n│   └── app.vue\n├── server/\n├── public/\n└── nuxt.config.ts\n",[34,61,59],{"__ignoreMap":49},[15,63,64,65,67,68,71],{},"Ce n'est pas qu'esthétique. La séparation nette entre le code applicatif (",[34,66,36],{},") et le code serveur (",[34,69,70],{},"server/",") clarifie les responsabilités, surtout sur des projets qui grossissent ou qui impliquent plusieurs devs.",[73,74,75],"blockquote",{},[15,76,77,78,81,82,85],{},"Pour activer ce comportement dès Nuxt 3.x, tu pouvais déjà le tester via ",[34,79,80],{},"future.compatibilityVersion: 4"," dans ",[34,83,84],{},"nuxt.config.ts",". En Nuxt 4, c'est le comportement par défaut.",[19,87,89,90,93,94],{"id":88},"les-data-fetchers-useasyncdata-et-usefetch","Les data fetchers : ",[34,91,92],{},"useAsyncData"," et ",[34,95,96],{},"useFetch",[15,98,99],{},"Rien de révolutionnaire ici, mais Nuxt 4 renforce les comportements attendus autour de la réactivité et des clés de cache.",[15,101,102,103,105,106,109],{},"En Nuxt 3, il arrivait qu'",[34,104,92],{}," ne se re-déclenche pas correctement quand une dépendance réactive changeait. En Nuxt 4, la gestion des ",[34,107,108],{},"watch"," internes est plus prévisible :",[41,111,115],{"className":112,"code":113,"language":114,"meta":49,"style":49},"language-ts shiki shiki-themes github-dark github-light","// Réactif à `route.params.id` sans configuration supplémentaire\nconst { data } = await useAsyncData(`product-${route.params.id}`, () =>\n  $fetch(`/api/products/${route.params.id}`),\n)\n","ts",[34,116,117,126,184,210],{"__ignoreMap":49},[118,119,122],"span",{"class":120,"line":121},"line",1,[118,123,125],{"class":124},"sryI4","// Réactif à `route.params.id` sans configuration supplémentaire\n",[118,127,129,133,137,141,144,147,150,154,157,161,164,167,170,172,175,178,181],{"class":120,"line":128},2,[118,130,132],{"class":131},"scx8i","const",[118,134,136],{"class":135},"sQ3_J"," { ",[118,138,140],{"class":139},"s0DvM","data",[118,142,143],{"class":135}," } ",[118,145,146],{"class":131},"=",[118,148,149],{"class":131}," await",[118,151,153],{"class":152},"s-Z4r"," useAsyncData",[118,155,156],{"class":135},"(",[118,158,160],{"class":159},"sg6BJ","`product-${",[118,162,163],{"class":135},"route",[118,165,166],{"class":159},".",[118,168,169],{"class":135},"params",[118,171,166],{"class":159},[118,173,174],{"class":135},"id",[118,176,177],{"class":159},"}`",[118,179,180],{"class":135},", () ",[118,182,183],{"class":131},"=>\n",[118,185,187,190,192,195,197,199,201,203,205,207],{"class":120,"line":186},3,[118,188,189],{"class":152},"  $fetch",[118,191,156],{"class":135},[118,193,194],{"class":159},"`/api/products/${",[118,196,163],{"class":135},[118,198,166],{"class":159},[118,200,169],{"class":135},[118,202,166],{"class":159},[118,204,174],{"class":135},[118,206,177],{"class":159},[118,208,209],{"class":135},"),\n",[118,211,213],{"class":120,"line":212},4,[118,214,215],{"class":135},")\n",[15,217,218],{},"La règle : la clé doit être unique et refléter les paramètres dynamiques. Si la clé est statique alors que les données varient, tu auras des problèmes de cache — c'est valable en Nuxt 3 aussi, mais Nuxt 4 rend ce point plus visible.",[19,220,222],{"id":221},"nitro-et-les-routes-serveur","Nitro et les routes serveur",[15,224,225,226,55],{},"Nuxt embarque Nitro comme runtime serveur. Les routes API se définissent dans ",[34,227,228],{},"server/api/",[41,230,232],{"className":112,"code":231,"language":114,"meta":49,"style":49},"// server/api/products/[id].get.ts\nexport default defineEventHandler(async (event) => {\n  const id = getRouterParam(event, \"id\")\n  const product = await db.products.findById(id)\n  if (!product) throw createError({ statusCode: 404 })\n  return product\n})\n",[34,233,234,239,271,293,313,342,351],{"__ignoreMap":49},[118,235,236],{"class":120,"line":121},[118,237,238],{"class":124},"// server/api/products/[id].get.ts\n",[118,240,241,244,247,250,252,255,258,262,265,268],{"class":120,"line":128},[118,242,243],{"class":131},"export",[118,245,246],{"class":131}," default",[118,248,249],{"class":152}," defineEventHandler",[118,251,156],{"class":135},[118,253,254],{"class":131},"async",[118,256,257],{"class":135}," (",[118,259,261],{"class":260},"sFbx2","event",[118,263,264],{"class":135},") ",[118,266,267],{"class":131},"=>",[118,269,270],{"class":135}," {\n",[118,272,273,276,279,282,285,288,291],{"class":120,"line":186},[118,274,275],{"class":131},"  const",[118,277,278],{"class":139}," id",[118,280,281],{"class":131}," =",[118,283,284],{"class":152}," getRouterParam",[118,286,287],{"class":135},"(event, ",[118,289,290],{"class":159},"\"id\"",[118,292,215],{"class":135},[118,294,295,297,300,302,304,307,310],{"class":120,"line":212},[118,296,275],{"class":131},[118,298,299],{"class":139}," product",[118,301,281],{"class":131},[118,303,149],{"class":131},[118,305,306],{"class":135}," db.products.",[118,308,309],{"class":152},"findById",[118,311,312],{"class":135},"(id)\n",[118,314,316,319,321,324,327,330,333,336,339],{"class":120,"line":315},5,[118,317,318],{"class":131},"  if",[118,320,257],{"class":135},[118,322,323],{"class":131},"!",[118,325,326],{"class":135},"product) ",[118,328,329],{"class":131},"throw",[118,331,332],{"class":152}," createError",[118,334,335],{"class":135},"({ statusCode: ",[118,337,338],{"class":139},"404",[118,340,341],{"class":135}," })\n",[118,343,345,348],{"class":120,"line":344},6,[118,346,347],{"class":131},"  return",[118,349,350],{"class":135}," product\n",[118,352,354],{"class":120,"line":353},7,[118,355,356],{"class":135},"})\n",[15,358,359,360,363,364,363,367,370],{},"Nitro compile ça en un bundle portable, déployable sur Node.js, edge workers (Cloudflare, Vercel), ou en statique. En Nuxt 4, la maturité de Nitro se ressent : les erreurs de typage sont mieux résolues et les helpers comme ",[34,361,362],{},"getRouterParam",", ",[34,365,366],{},"readBody",[34,368,369],{},"getCookie"," sont plus robustes.",[19,372,374],{"id":373},"les-composables-auto-importés","Les composables auto-importés",[15,376,377,378,381,382,385],{},"Nuxt auto-importe les composables placés dans ",[34,379,380],{},"app/composables/",". Pas de ",[34,383,384],{},"import"," à écrire, ils sont disponibles partout dans l'app :",[41,387,389],{"className":112,"code":388,"language":114,"meta":49,"style":49},"// app/composables/useApi.ts\nexport const useApi = () => {\n  const config = useRuntimeConfig()\n  return $fetch.create({ baseURL: config.public.apiBase })\n}\n\n// Dans un composant, directement :\nconst api = useApi()\nconst data = await api(\"/products\")\n",[34,390,391,396,415,430,443,448,454,459,473],{"__ignoreMap":49},[118,392,393],{"class":120,"line":121},[118,394,395],{"class":124},"// app/composables/useApi.ts\n",[118,397,398,400,403,406,408,411,413],{"class":120,"line":128},[118,399,243],{"class":131},[118,401,402],{"class":131}," const",[118,404,405],{"class":152}," useApi",[118,407,281],{"class":131},[118,409,410],{"class":135}," () ",[118,412,267],{"class":131},[118,414,270],{"class":135},[118,416,417,419,422,424,427],{"class":120,"line":186},[118,418,275],{"class":131},[118,420,421],{"class":139}," config",[118,423,281],{"class":131},[118,425,426],{"class":152}," useRuntimeConfig",[118,428,429],{"class":135},"()\n",[118,431,432,434,437,440],{"class":120,"line":212},[118,433,347],{"class":131},[118,435,436],{"class":135}," $fetch.",[118,438,439],{"class":152},"create",[118,441,442],{"class":135},"({ baseURL: config.public.apiBase })\n",[118,444,445],{"class":120,"line":315},[118,446,447],{"class":135},"}\n",[118,449,450],{"class":120,"line":344},[118,451,453],{"emptyLinePlaceholder":452},true,"\n",[118,455,456],{"class":120,"line":353},[118,457,458],{"class":124},"// Dans un composant, directement :\n",[118,460,462,464,467,469,471],{"class":120,"line":461},8,[118,463,132],{"class":131},[118,465,466],{"class":139}," api",[118,468,281],{"class":131},[118,470,405],{"class":152},[118,472,429],{"class":135},[118,474,476,478,481,483,485,487,489,492],{"class":120,"line":475},9,[118,477,132],{"class":131},[118,479,480],{"class":139}," data",[118,482,281],{"class":131},[118,484,149],{"class":131},[118,486,466],{"class":152},[118,488,156],{"class":135},[118,490,491],{"class":159},"\"/products\"",[118,493,215],{"class":135},[15,495,496],{},"C'est pratique, mais attention : sur les gros projets, ça peut rendre le code moins lisible si les conventions ne sont pas respectées. Une règle simple : un fichier par composable, nommé explicitement.",[19,498,500],{"id":499},"nuxt-content-v3","Nuxt Content v3",[15,502,503],{},"Si tu utilises Nuxt pour un blog ou de la documentation, Nuxt Content v3 (compatible Nuxt 4) change la donne. Le parsing MDC (Markdown avec composants Vue) est plus rapide, et l'API de requête des contenus est typée :",[41,505,507],{"className":112,"code":506,"language":114,"meta":49,"style":49},"const { data } = await useAsyncData(\"articles\", () =>\n  queryCollection(\"blog\")\n    .where(\"published\", \"=\", true)\n    .order(\"date\", \"DESC\")\n    .all(),\n)\n",[34,508,509,534,546,571,590,600],{"__ignoreMap":49},[118,510,511,513,515,517,519,521,523,525,527,530,532],{"class":120,"line":121},[118,512,132],{"class":131},[118,514,136],{"class":135},[118,516,140],{"class":139},[118,518,143],{"class":135},[118,520,146],{"class":131},[118,522,149],{"class":131},[118,524,153],{"class":152},[118,526,156],{"class":135},[118,528,529],{"class":159},"\"articles\"",[118,531,180],{"class":135},[118,533,183],{"class":131},[118,535,536,539,541,544],{"class":120,"line":128},[118,537,538],{"class":152},"  queryCollection",[118,540,156],{"class":135},[118,542,543],{"class":159},"\"blog\"",[118,545,215],{"class":135},[118,547,548,551,554,556,559,561,564,566,569],{"class":120,"line":186},[118,549,550],{"class":135},"    .",[118,552,553],{"class":152},"where",[118,555,156],{"class":135},[118,557,558],{"class":159},"\"published\"",[118,560,363],{"class":135},[118,562,563],{"class":159},"\"=\"",[118,565,363],{"class":135},[118,567,568],{"class":139},"true",[118,570,215],{"class":135},[118,572,573,575,578,580,583,585,588],{"class":120,"line":212},[118,574,550],{"class":135},[118,576,577],{"class":152},"order",[118,579,156],{"class":135},[118,581,582],{"class":159},"\"date\"",[118,584,363],{"class":135},[118,586,587],{"class":159},"\"DESC\"",[118,589,215],{"class":135},[118,591,592,594,597],{"class":120,"line":315},[118,593,550],{"class":135},[118,595,596],{"class":152},"all",[118,598,599],{"class":135},"(),\n",[118,601,602],{"class":120,"line":344},[118,603,215],{"class":135},[15,605,606,607,55],{},"La configuration se fait dans ",[34,608,84],{},[41,610,612],{"className":112,"code":611,"language":114,"meta":49,"style":49},"export default defineNuxtConfig({\n  modules: [\"@nuxt/content\"],\n  content: {\n    build: {\n      markdown: {\n        highlight: { theme: \"github-dark\" },\n      },\n    },\n  },\n})\n",[34,613,614,626,637,642,647,652,663,668,673,678],{"__ignoreMap":49},[118,615,616,618,620,623],{"class":120,"line":121},[118,617,243],{"class":131},[118,619,246],{"class":131},[118,621,622],{"class":152}," defineNuxtConfig",[118,624,625],{"class":135},"({\n",[118,627,628,631,634],{"class":120,"line":128},[118,629,630],{"class":135},"  modules: [",[118,632,633],{"class":159},"\"@nuxt/content\"",[118,635,636],{"class":135},"],\n",[118,638,639],{"class":120,"line":186},[118,640,641],{"class":135},"  content: {\n",[118,643,644],{"class":120,"line":212},[118,645,646],{"class":135},"    build: {\n",[118,648,649],{"class":120,"line":315},[118,650,651],{"class":135},"      markdown: {\n",[118,653,654,657,660],{"class":120,"line":344},[118,655,656],{"class":135},"        highlight: { theme: ",[118,658,659],{"class":159},"\"github-dark\"",[118,661,662],{"class":135}," },\n",[118,664,665],{"class":120,"line":353},[118,666,667],{"class":135},"      },\n",[118,669,670],{"class":120,"line":461},[118,671,672],{"class":135},"    },\n",[118,674,675],{"class":120,"line":475},[118,676,677],{"class":135},"  },\n",[118,679,681],{"class":120,"line":680},10,[118,682,356],{"class":135},[19,684,686],{"id":685},"ce-quil-faut-retenir","Ce qu'il faut retenir",[15,688,689],{},"Nuxt 4 consolide ce que Nuxt 3 a introduit. Les changements sont incrémentaux mais cohérents : meilleure séparation app/serveur, réactivité plus fiable, Nitro plus mature. Si tu pars sur un nouveau projet Vue.js avec du SSR, c'est le point de départ raisonnable aujourd'hui.",[15,691,692,693,696,697,699],{},"La migration depuis Nuxt 3 est progressive — le flag ",[34,694,695],{},"compatibilityVersion: 4"," permet de tester les nouveaux comportements sans tout casser d'un coup. Sur mon portfolio personnel (Nuxt 3 → Nuxt 4), la migration a pris une après-midi, principalement pour réorganiser les fichiers dans ",[34,698,36],{}," et ajuster quelques imports.",[701,702,703],"style",{},"html pre.shiki code .sryI4, html code.shiki .sryI4{--shiki-dark:#6A737D;--shiki-default:#6A737D}html pre.shiki code .scx8i, html code.shiki .scx8i{--shiki-dark:#F97583;--shiki-default:#D73A49}html pre.shiki code .sQ3_J, html code.shiki .sQ3_J{--shiki-dark:#E1E4E8;--shiki-default:#24292E}html pre.shiki code .s0DvM, html code.shiki .s0DvM{--shiki-dark:#79B8FF;--shiki-default:#005CC5}html pre.shiki code .s-Z4r, html code.shiki .s-Z4r{--shiki-dark:#B392F0;--shiki-default:#6F42C1}html pre.shiki code .sg6BJ, html code.shiki .sg6BJ{--shiki-dark:#9ECBFF;--shiki-default:#032F62}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sFbx2, html code.shiki .sFbx2{--shiki-dark:#FFAB70;--shiki-default:#E36209}",{"title":49,"searchDepth":128,"depth":128,"links":705},[706,707,709,711,712,713,714],{"id":21,"depth":128,"text":22},{"id":31,"depth":128,"text":708},"Le changement principal : le répertoire app/",{"id":88,"depth":128,"text":710},"Les data fetchers : useAsyncData et useFetch",{"id":221,"depth":128,"text":222},{"id":373,"depth":128,"text":374},{"id":499,"depth":128,"text":500},{"id":685,"depth":128,"text":686},"2024-12-10",null,"md",{},"/fr/blog/nuxt4-introduction",{"title":5,"description":17},"nuxt4-introduction","fr/blog/nuxt4-introduction",[724,725,726,727],"Nuxt","Vue.js","Frontend","SSR","ALbyP1ko0eISYeHct2NnKw0bNNKvoNMmn7CSINI_9Z4",1774645636242]