[{"data":1,"prerenderedAt":1381},["ShallowReactive",2],{"/blog/lighthouse":3,"/blog/lighthouse-surround":1372},{"id":4,"title":5,"body":6,"category":1358,"description":1359,"extension":1360,"image":1361,"meta":1363,"navigation":779,"path":1364,"publishedAt":1365,"seo":1366,"sitemap":1367,"stem":1368,"summary":1369,"updatedAt":1370,"__hash__":1371},"blog_articles/blog/lighthouse.md","Comment atteindre 100/100 sur Lighthouse avec Nuxt 4",{"type":7,"value":8,"toc":1341},"minimark",[9,22,29,32,37,40,76,79,83,88,91,103,280,285,304,308,315,319,323,326,329,332,337,450,456,461,560,579,583,586,590,593,596,730,734,741,744,847,851,854,857,1038,1041,1048,1052,1055,1058,1065,1072,1281,1288,1291,1299,1303,1306,1309,1316,1319,1322,1337],[10,11,12,13,17,18,21],"p",{},"L'",[14,15,16],"strong",{},"optimisation de la performance Nuxt 4"," n'est plus optionnelle en 2026. Si vous vous demandez pourquoi vos concurrents vous dépassent sur Google malgré un design plus daté, la réponse tient souvent en un seul mot : ",[14,19,20],{},"vitesse",".",[10,23,24,25,28],{},"Un score ",[14,26,27],{},"Lighthouse à 100/100",", ce n'est pas juste un badge. C'est un taux de conversion plus élevé, des coûts d'acquisition client réduits, et un signal clair envoyé à Google qui privilégie de plus en plus l'expérience utilisateur réelle.",[10,30,31],{},"Dans cet article, je vous montre exactement les techniques que j'applique sur chaque projet pour atteindre ce score de 100/100.",[33,34,36],"h2",{"id":35},"les-3-métriques-core-web-vitals-à-maîtriser-en-2026","Les 3 métriques Core Web Vitals à maîtriser en 2026",[10,38,39],{},"Google a affiné ses exigences. On ne mesure plus simplement le temps de réponse brut du serveur (bien que le TTFB reste crucial). On mesure la perception humaine du chargement, découpée en trois dimensions fondamentales :",[41,42,43,57,66],"ol",{},[44,45,46,49,50,53,54,21],"li",{},[14,47,48],{},"LCP (Largest Contentful Paint) :"," C'est le chronomètre qui s'arrête lorsque le plus gros élément de votre page (souvent l'image \"Hero\" ou le gros titre H1) est visible. La règle est simple : ",[14,51,52],{},"moins de 2.5 secondes"," pour être \"Bon\", mais en réalité, je vise systématiquement ",[14,55,56],{},"moins de 1 seconde",[44,58,59,62,63,21],{},[14,60,61],{},"INP (Interaction to Next Paint) :"," Fini le FID. L'INP mesure la latence de l'interface complète après une action utilisateur (un clic, un tapotement). Une interface qui \"freeze\" pendant 300 millisecondes vous pénalise. L'objectif : ",[14,64,65],{},"moins de 200ms",[44,67,68,71,72,75],{},[14,69,70],{},"CLS (Cumulative Layout Shift) :"," Rien n'est plus frustrant qu'un texte qui décale d'un coup parce qu'une bannière publicitaire ou une police de caractères vient de charger. Le CLS mesure cette instabilité visuelle. Le score doit être quasi nul (",[14,73,74],{},"inférieur à 0.1",").",[10,77,78],{},"Pour atteindre un 100/100 sur ces trois métriques avec un framework riche comme Vue.js (qui embarque naturellement plus de JavaScript qu'une page statique), il faut être méthodique.",[33,80,82],{"id":81},"lcp-charger-lessentiel-en-moins-dune-seconde","LCP : Charger l'essentiel en moins d'une seconde",[84,85,87],"h3",{"id":86},"_1-précharger-agressivement-le-contenu-critique","1. Précharger agressivement le contenu critique",[10,89,90],{},"Le principal ennemi du LCP, c'est le navigateur qui découvre tardivement qu'il a besoin d'une grosse ressource pour afficher le haut de la page.",[10,92,93,94,97,98,102],{},"Pour ",[14,95,96],{},"améliorer le LCP sous Nuxt",", le composant natif ",[99,100,101],"code",{},"@nuxt/image"," est mon premier réflexe. Il s'occupe de la compression (WebP / AVIF) et génère le code HTML optimal. Mais cela ne suffit pas pour le plafond de verre des 100/100. Il faut guider le navigateur.",[104,105,110],"pre",{"className":106,"code":107,"language":108,"meta":109,"style":109},"language-vue shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003Ctemplate>\n  \u003CNuxtImg \n    src=\"/hero-banner.jpg\" \n    alt=\"Bannière de la solution SaaS\"\n    width=\"1200\" \n    height=\"600\" \n    format=\"webp\" \n    loading=\"eager\"\n    fetchpriority=\"high\"\n    preload\n  />\n\u003C/template>\n","vue","",[99,111,112,128,140,161,177,194,211,228,243,258,264,270],{"__ignoreMap":109},[113,114,117,121,125],"span",{"class":115,"line":116},"line",1,[113,118,120],{"class":119},"sMK4o","\u003C",[113,122,124],{"class":123},"swJcz","template",[113,126,127],{"class":119},">\n",[113,129,131,134,137],{"class":115,"line":130},2,[113,132,133],{"class":119},"  \u003C",[113,135,136],{"class":123},"NuxtImg",[113,138,139],{"class":119}," \n",[113,141,143,147,150,153,157,159],{"class":115,"line":142},3,[113,144,146],{"class":145},"spNyl","    src",[113,148,149],{"class":119},"=",[113,151,152],{"class":119},"\"",[113,154,156],{"class":155},"sfazB","/hero-banner.jpg",[113,158,152],{"class":119},[113,160,139],{"class":119},[113,162,164,167,169,171,174],{"class":115,"line":163},4,[113,165,166],{"class":145},"    alt",[113,168,149],{"class":119},[113,170,152],{"class":119},[113,172,173],{"class":155},"Bannière de la solution SaaS",[113,175,176],{"class":119},"\"\n",[113,178,180,183,185,187,190,192],{"class":115,"line":179},5,[113,181,182],{"class":145},"    width",[113,184,149],{"class":119},[113,186,152],{"class":119},[113,188,189],{"class":155},"1200",[113,191,152],{"class":119},[113,193,139],{"class":119},[113,195,197,200,202,204,207,209],{"class":115,"line":196},6,[113,198,199],{"class":145},"    height",[113,201,149],{"class":119},[113,203,152],{"class":119},[113,205,206],{"class":155},"600",[113,208,152],{"class":119},[113,210,139],{"class":119},[113,212,214,217,219,221,224,226],{"class":115,"line":213},7,[113,215,216],{"class":145},"    format",[113,218,149],{"class":119},[113,220,152],{"class":119},[113,222,223],{"class":155},"webp",[113,225,152],{"class":119},[113,227,139],{"class":119},[113,229,231,234,236,238,241],{"class":115,"line":230},8,[113,232,233],{"class":145},"    loading",[113,235,149],{"class":119},[113,237,152],{"class":119},[113,239,240],{"class":155},"eager",[113,242,176],{"class":119},[113,244,246,249,251,253,256],{"class":115,"line":245},9,[113,247,248],{"class":145},"    fetchpriority",[113,250,149],{"class":119},[113,252,152],{"class":119},[113,254,255],{"class":155},"high",[113,257,176],{"class":119},[113,259,261],{"class":115,"line":260},10,[113,262,263],{"class":145},"    preload\n",[113,265,267],{"class":115,"line":266},11,[113,268,269],{"class":119},"  />\n",[113,271,273,276,278],{"class":115,"line":272},12,[113,274,275],{"class":119},"\u003C/",[113,277,124],{"class":123},[113,279,127],{"class":119},[10,281,282],{},[14,283,284],{},"Pourquoi ça marche ?",[286,287,288,294],"ul",{},[44,289,290,293],{},[99,291,292],{},"fetchpriority=\"high\""," dit au navigateur d'ignorer la file d'attente classique et de télécharger ce fichier en urgence absolue.",[44,295,296,299,300,303],{},[99,297,298],{},"preload"," insère une balise dans le ",[99,301,302],{},"\u003Chead>"," pour lancer le téléchargement avant même que le moteur de rendu ne calcule le Layout.",[84,305,307],{"id":306},"_2-le-piège-des-polices-de-caractères-personnalisées","2. Le piège des polices de caractères personnalisées",[10,309,310,311,314],{},"Le LCP peut être retardé si le navigateur attend une grosse police Google Fonts avant de dessiner votre H1. Ma recommandation : auto-hébergez vos polices grâce au module ",[99,312,313],{},"@nuxt/fonts",". Nuxt s'occupera de précharger uniquement les glyphes nécessaires au SSR.",[33,316,318],{"id":317},"inp-nuxt-seo-la-fluidité-absolue","INP & Nuxt SEO : La fluidité absolue",[84,320,322],{"id":321},"maîtriser-le-chargement-des-composants-lazyloading","Maîtriser le chargement des composants (LazyLoading)",[10,324,325],{},"Le framework Vue.js est fantastique, mais c'est une arme à double tranchant. Si vous importez tous vos composants complexes (modales, graphiques, librairies tierces) dès la page d'accueil, le fichier JavaScript final (le \"chunk\" initial) va gonfler. Le temps de Parse et d'Évaluation JavaScript va exploser, détruisant votre INP.",[10,327,328],{},"La solution est au cœur de l'approche Nuxt 4 : les imports dynamiques. Ne téléchargez un composant que si l'utilisateur en a besoin.",[10,330,331],{},"Prenons l'exemple d'une modale contenant un long formulaire de contact.",[10,333,334],{},[14,335,336],{},"Ce qu'il ne faut PAS faire :",[104,338,340],{"className":106,"code":339,"language":108,"meta":109,"style":109},"\u003Ctemplate>\n  \u003Cdiv>\n    \u003Cbutton @click=\"isOpen = true\">Me contacter\u003C/button>\n    \u003CModalFormulaire v-if=\"isOpen\">\n      \u003CMonSuperFormulaireLourd />\n    \u003C/ModalFormulaire>\n  \u003C/div>\n\u003C/template>\n",[99,341,342,350,359,392,413,424,433,442],{"__ignoreMap":109},[113,343,344,346,348],{"class":115,"line":116},[113,345,120],{"class":119},[113,347,124],{"class":123},[113,349,127],{"class":119},[113,351,352,354,357],{"class":115,"line":130},[113,353,133],{"class":119},[113,355,356],{"class":123},"div",[113,358,127],{"class":119},[113,360,361,364,367,370,372,374,377,379,382,386,388,390],{"class":115,"line":142},[113,362,363],{"class":119},"    \u003C",[113,365,366],{"class":123},"button",[113,368,369],{"class":145}," @click",[113,371,149],{"class":119},[113,373,152],{"class":119},[113,375,376],{"class":155},"isOpen = true",[113,378,152],{"class":119},[113,380,381],{"class":119},">",[113,383,385],{"class":384},"sTEyZ","Me contacter",[113,387,275],{"class":119},[113,389,366],{"class":123},[113,391,127],{"class":119},[113,393,394,396,399,402,404,406,409,411],{"class":115,"line":163},[113,395,363],{"class":119},[113,397,398],{"class":123},"ModalFormulaire",[113,400,401],{"class":145}," v-if",[113,403,149],{"class":119},[113,405,152],{"class":119},[113,407,408],{"class":155},"isOpen",[113,410,152],{"class":119},[113,412,127],{"class":119},[113,414,415,418,421],{"class":115,"line":179},[113,416,417],{"class":119},"      \u003C",[113,419,420],{"class":123},"MonSuperFormulaireLourd",[113,422,423],{"class":119}," />\n",[113,425,426,429,431],{"class":115,"line":196},[113,427,428],{"class":119},"    \u003C/",[113,430,398],{"class":123},[113,432,127],{"class":119},[113,434,435,438,440],{"class":115,"line":213},[113,436,437],{"class":119},"  \u003C/",[113,439,356],{"class":123},[113,441,127],{"class":119},[113,443,444,446,448],{"class":115,"line":230},[113,445,275],{"class":119},[113,447,124],{"class":123},[113,449,127],{"class":119},[10,451,452,453,455],{},"Dans l'exemple ci-dessus, le code du composant ",[99,454,420],{}," est téléchargé et bloquant, même si 90% des utilisateurs ne cliqueront jamais sur le bouton.",[10,457,458],{},[14,459,460],{},"Ce qu'il FAUT faire :",[104,462,464],{"className":106,"code":463,"language":108,"meta":109,"style":109},"\u003Ctemplate>\n  \u003Cdiv>\n    \u003Cbutton @click=\"isOpen = true\">Me contacter\u003C/button>\n    \u003CLazyModalFormulaire v-if=\"isOpen\">\n      \u003CLazyMonSuperFormulaireLourd />\n    \u003C/LazyModalFormulaire>\n  \u003C/div>\n\u003C/template>\n",[99,465,466,474,482,508,527,536,544,552],{"__ignoreMap":109},[113,467,468,470,472],{"class":115,"line":116},[113,469,120],{"class":119},[113,471,124],{"class":123},[113,473,127],{"class":119},[113,475,476,478,480],{"class":115,"line":130},[113,477,133],{"class":119},[113,479,356],{"class":123},[113,481,127],{"class":119},[113,483,484,486,488,490,492,494,496,498,500,502,504,506],{"class":115,"line":142},[113,485,363],{"class":119},[113,487,366],{"class":123},[113,489,369],{"class":145},[113,491,149],{"class":119},[113,493,152],{"class":119},[113,495,376],{"class":155},[113,497,152],{"class":119},[113,499,381],{"class":119},[113,501,385],{"class":384},[113,503,275],{"class":119},[113,505,366],{"class":123},[113,507,127],{"class":119},[113,509,510,512,515,517,519,521,523,525],{"class":115,"line":163},[113,511,363],{"class":119},[113,513,514],{"class":123},"LazyModalFormulaire",[113,516,401],{"class":145},[113,518,149],{"class":119},[113,520,152],{"class":119},[113,522,408],{"class":155},[113,524,152],{"class":119},[113,526,127],{"class":119},[113,528,529,531,534],{"class":115,"line":179},[113,530,417],{"class":119},[113,532,533],{"class":123},"LazyMonSuperFormulaireLourd",[113,535,423],{"class":119},[113,537,538,540,542],{"class":115,"line":196},[113,539,428],{"class":119},[113,541,514],{"class":123},[113,543,127],{"class":119},[113,545,546,548,550],{"class":115,"line":213},[113,547,437],{"class":119},[113,549,356],{"class":123},[113,551,127],{"class":119},[113,553,554,556,558],{"class":115,"line":230},[113,555,275],{"class":119},[113,557,124],{"class":123},[113,559,127],{"class":119},[10,561,562,563,566,567,571,572,574,575,578],{},"Le simple ajout du préfixe magique ",[99,564,565],{},"Lazy"," indique au compilateur Vite (utilisé par Nuxt) de découper ce composant dans un fichier JavaScript séparé, qui ne sera téléchargé ",[568,569,570],"em",{},"uniquement"," que lorsque ",[99,573,408],{}," deviendra ",[99,576,577],{},"true",". Résultat ? Un INP imbattable.",[33,580,582],{"id":581},"cls-éradiquer-les-instabilités-visuelles","CLS : Éradiquer les instabilités visuelles",[10,584,585],{},"Le Cumulative Layout Shift est souvent le plus difficile à débugger. Deux erreurs fréquentes provoquent des décalages visuels (surtout lors de la phase d'Hydratation).",[84,587,589],{"id":588},"_1-toujours-réserver-de-lespace","1. Toujours réserver de l'espace",[10,591,592],{},"Lorsqu'un composant dépend de données chargées asynchronement (depuis une base de données par exemple), l'espace vide est une bombe à retardement pour le CLS.",[10,594,595],{},"L'astuce consiste à toujours anticiper la taille finale de l'élément pendant le chargement en utilisant des squelettes (Skeletons) CSS purs.",[104,597,599],{"className":106,"code":598,"language":108,"meta":109,"style":109},"\u003Ctemplate>\n  \u003Cdiv class=\"h-64 mt-4 w-full\">\n    \u003C!-- Réservation stricte de la hauteur avec un composant Squelette -->\n    \u003CSkeletonLoader class=\"w-full h-full\" v-if=\"pending\" />\n    \u003CCardArticle v-else class=\"h-full\">\n      \u003Ch3>{{ article.title }}\u003C/h3>\n    \u003C/CardArticle>\n  \u003C/div>\n\u003C/template>\n",[99,600,601,609,629,635,666,689,706,714,722],{"__ignoreMap":109},[113,602,603,605,607],{"class":115,"line":116},[113,604,120],{"class":119},[113,606,124],{"class":123},[113,608,127],{"class":119},[113,610,611,613,615,618,620,622,625,627],{"class":115,"line":130},[113,612,133],{"class":119},[113,614,356],{"class":123},[113,616,617],{"class":145}," class",[113,619,149],{"class":119},[113,621,152],{"class":119},[113,623,624],{"class":155},"h-64 mt-4 w-full",[113,626,152],{"class":119},[113,628,127],{"class":119},[113,630,631],{"class":115,"line":142},[113,632,634],{"class":633},"sHwdD","    \u003C!-- Réservation stricte de la hauteur avec un composant Squelette -->\n",[113,636,637,639,642,644,646,648,651,653,655,657,659,662,664],{"class":115,"line":163},[113,638,363],{"class":119},[113,640,641],{"class":123},"SkeletonLoader",[113,643,617],{"class":145},[113,645,149],{"class":119},[113,647,152],{"class":119},[113,649,650],{"class":155},"w-full h-full",[113,652,152],{"class":119},[113,654,401],{"class":145},[113,656,149],{"class":119},[113,658,152],{"class":119},[113,660,661],{"class":155},"pending",[113,663,152],{"class":119},[113,665,423],{"class":119},[113,667,668,670,673,676,678,680,682,685,687],{"class":115,"line":179},[113,669,363],{"class":119},[113,671,672],{"class":123},"CardArticle",[113,674,675],{"class":145}," v-else",[113,677,617],{"class":145},[113,679,149],{"class":119},[113,681,152],{"class":119},[113,683,684],{"class":155},"h-full",[113,686,152],{"class":119},[113,688,127],{"class":119},[113,690,691,693,695,697,700,702,704],{"class":115,"line":196},[113,692,417],{"class":119},[113,694,84],{"class":123},[113,696,381],{"class":119},[113,698,699],{"class":384},"{{ article.title }}",[113,701,275],{"class":119},[113,703,84],{"class":123},[113,705,127],{"class":119},[113,707,708,710,712],{"class":115,"line":213},[113,709,428],{"class":119},[113,711,672],{"class":123},[113,713,127],{"class":119},[113,715,716,718,720],{"class":115,"line":230},[113,717,437],{"class":119},[113,719,356],{"class":123},[113,721,127],{"class":119},[113,723,724,726,728],{"class":115,"line":245},[113,725,275],{"class":119},[113,727,124],{"class":123},[113,729,127],{"class":119},[84,731,733],{"id":732},"_2-le-problème-des-icônes","2. Le problème des Icônes",[10,735,736,737,740],{},"Un piège classique avec l'intégration d'icônes SVG en ligne ou via des modules comme ",[99,738,739],{},"@nuxt/icon"," : les icônes mettent parfois quelques millisecondes à se charger côté client. Si vous ne forcez pas les dimensions CSS de l'icône, le conteneur va tressauter de quelques pixels une fois l'icône affichée.",[10,742,743],{},"Prenez le réflexe de toujours fixer des largeurs fermes dans votre CSS (ici avec Tailwind) :",[104,745,747],{"className":106,"code":746,"language":108,"meta":109,"style":109},"\u003C!-- Mauvais -->\n\u003CIcon name=\"heroicons:light-bulb\" />\n\n\u003C!-- Bon -->\n\u003CIcon name=\"heroicons:light-bulb\" class=\"w-5 h-5 flex-shrink-0\" />\n\u003C!-- Bon -->\n\u003CIcon name=\"heroicons:light-bulb\" class=\"w-5 h-5 flex-shrink-0\" />\n",[99,748,749,754,775,781,786,815,819],{"__ignoreMap":109},[113,750,751],{"class":115,"line":116},[113,752,753],{"class":633},"\u003C!-- Mauvais -->\n",[113,755,756,758,761,764,766,768,771,773],{"class":115,"line":130},[113,757,120],{"class":119},[113,759,760],{"class":123},"Icon",[113,762,763],{"class":145}," name",[113,765,149],{"class":119},[113,767,152],{"class":119},[113,769,770],{"class":155},"heroicons:light-bulb",[113,772,152],{"class":119},[113,774,423],{"class":119},[113,776,777],{"class":115,"line":142},[113,778,780],{"emptyLinePlaceholder":779},true,"\n",[113,782,783],{"class":115,"line":163},[113,784,785],{"class":633},"\u003C!-- Bon -->\n",[113,787,788,790,792,794,796,798,800,802,804,806,808,811,813],{"class":115,"line":179},[113,789,120],{"class":119},[113,791,760],{"class":123},[113,793,763],{"class":145},[113,795,149],{"class":119},[113,797,152],{"class":119},[113,799,770],{"class":155},[113,801,152],{"class":119},[113,803,617],{"class":145},[113,805,149],{"class":119},[113,807,152],{"class":119},[113,809,810],{"class":155},"w-5 h-5 flex-shrink-0",[113,812,152],{"class":119},[113,814,423],{"class":119},[113,816,817],{"class":115,"line":196},[113,818,785],{"class":633},[113,820,821,823,825,827,829,831,833,835,837,839,841,843,845],{"class":115,"line":213},[113,822,120],{"class":119},[113,824,760],{"class":123},[113,826,763],{"class":145},[113,828,149],{"class":119},[113,830,152],{"class":119},[113,832,770],{"class":155},[113,834,152],{"class":119},[113,836,617],{"class":145},[113,838,149],{"class":119},[113,840,152],{"class":119},[113,842,810],{"class":155},[113,844,152],{"class":119},[113,846,423],{"class":119},[84,848,850],{"id":849},"_3-désactiver-les-effets-lourds-sur-mobile","3. Désactiver les effets lourds sur mobile",[10,852,853],{},"Les animations canvas, les particules et les effets visuels spectaculaires qui fonctionnent parfaitement sur desktop peuvent transformer votre site en diaporama glacial sur mobile. Le CPU des appareils mobiles n'a pas la puissance de calcul d'un laptop, et votre INP en pâtit directement.",[10,855,856],{},"La solution : une détection ciblée qui désactive ces effets cuando ils ne peuvent pas s'exprimer correctement.",[104,858,860],{"className":106,"code":859,"language":108,"meta":109,"style":109},"\u003Cscript setup>\nonMounted(() => {\n  // Détecter le contexte mobile\n  const isMobile = window.innerWidth \u003C 768\n  \n  // Respecter les préférences d'accessibilité utilisateur\n  const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches\n  \n  if (isMobile || prefersReducedMotion) {\n    // Arrêter les animations coûteuses\n    return\n  }\n  \n  // Initialiser l'effet canvas...\n})\n\u003C/script>\n",[99,861,862,874,892,897,923,928,933,967,971,994,999,1004,1009,1014,1020,1029],{"__ignoreMap":109},[113,863,864,866,869,872],{"class":115,"line":116},[113,865,120],{"class":119},[113,867,868],{"class":123},"script",[113,870,871],{"class":145}," setup",[113,873,127],{"class":119},[113,875,876,880,883,886,889],{"class":115,"line":130},[113,877,879],{"class":878},"s2Zo4","onMounted",[113,881,882],{"class":384},"(",[113,884,885],{"class":119},"()",[113,887,888],{"class":145}," =>",[113,890,891],{"class":119}," {\n",[113,893,894],{"class":115,"line":142},[113,895,896],{"class":633},"  // Détecter le contexte mobile\n",[113,898,899,902,905,908,911,913,916,919],{"class":115,"line":163},[113,900,901],{"class":145},"  const",[113,903,904],{"class":384}," isMobile",[113,906,907],{"class":119}," =",[113,909,910],{"class":384}," window",[113,912,21],{"class":119},[113,914,915],{"class":384},"innerWidth",[113,917,918],{"class":119}," \u003C",[113,920,922],{"class":921},"sbssI"," 768\n",[113,924,925],{"class":115,"line":179},[113,926,927],{"class":123},"  \n",[113,929,930],{"class":115,"line":196},[113,931,932],{"class":633},"  // Respecter les préférences d'accessibilité utilisateur\n",[113,934,935,937,940,942,944,946,949,951,954,957,959,962,964],{"class":115,"line":213},[113,936,901],{"class":145},[113,938,939],{"class":384}," prefersReducedMotion",[113,941,907],{"class":119},[113,943,910],{"class":384},[113,945,21],{"class":119},[113,947,948],{"class":878},"matchMedia",[113,950,882],{"class":123},[113,952,953],{"class":119},"'",[113,955,956],{"class":155},"(prefers-reduced-motion: reduce)",[113,958,953],{"class":119},[113,960,961],{"class":123},")",[113,963,21],{"class":119},[113,965,966],{"class":384},"matches\n",[113,968,969],{"class":115,"line":230},[113,970,927],{"class":123},[113,972,973,977,980,983,986,988,991],{"class":115,"line":245},[113,974,976],{"class":975},"s7zQu","  if",[113,978,979],{"class":123}," (",[113,981,982],{"class":384},"isMobile",[113,984,985],{"class":119}," ||",[113,987,939],{"class":384},[113,989,990],{"class":123},") ",[113,992,993],{"class":119},"{\n",[113,995,996],{"class":115,"line":260},[113,997,998],{"class":633},"    // Arrêter les animations coûteuses\n",[113,1000,1001],{"class":115,"line":266},[113,1002,1003],{"class":975},"    return\n",[113,1005,1006],{"class":115,"line":272},[113,1007,1008],{"class":119},"  }\n",[113,1010,1012],{"class":115,"line":1011},13,[113,1013,927],{"class":123},[113,1015,1017],{"class":115,"line":1016},14,[113,1018,1019],{"class":633},"  // Initialiser l'effet canvas...\n",[113,1021,1023,1026],{"class":115,"line":1022},15,[113,1024,1025],{"class":119},"}",[113,1027,1028],{"class":384},")\n",[113,1030,1032,1034,1036],{"class":115,"line":1031},16,[113,1033,275],{"class":119},[113,1035,868],{"class":123},[113,1037,127],{"class":119},[10,1039,1040],{},"Pour les systèmes de particules (type étoilesAnimated ou confettis), je vais plus loin : je réduis drastiquement le nombre d'éléments sur mobile. 300 étoiles sur desktop, 80 sur mobile. L'œil humain ne fait pas la différence, mais le GPU oui.",[10,1042,1043,1044,1047],{},"Cette optimisation peut représenter ",[14,1045,1046],{},"10 à 20 points de score Lighthouse"," sur mobile.",[33,1049,1051],{"id":1050},"le-ssr-hybride-et-ledge-sur-vercel","Le SSR Hybride et l'Edge sur Vercel",[10,1053,1054],{},"Toutes les optimisations front-end du monde ne sauveront pas un serveur lent au démarrage.",[10,1056,1057],{},"Rendre la page sur un serveur traditionnel en Node.js (un VPS à Paris, par exemple) ajoute de la latence. Si votre visiteur est à Tokyo ou à New York, il attendra que la donnée traverse l'océan.",[10,1059,1060,1061,1064],{},"C'est là que le combo ",[14,1062,1063],{},"Nuxt 4 + Nitro + Vercel"," change la donne. En déployant sur l'infrastructure Edge de Vercel, le code SSR de votre application est distribué sur des centaines de datacenters dans le monde. Votre site s'exécute à quelques kilomètres de votre utilisateur.",[10,1066,1067,1068,1071],{},"Voici la configuration redoutable (et obligatoire) que j'utilise dans ",[99,1069,1070],{},"nuxt.config.ts"," :",[104,1073,1077],{"className":1074,"code":1075,"language":1076,"meta":109,"style":109},"language-typescript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","export default defineNuxtConfig({\n  nitro: {\n    // Active le déploiement mondial sur Vercel Edge\n    preset: 'vercel',\n    prerender: {\n      crawlLinks: true,\n      routes: ['/']\n    }\n  },\n  \n  routeRules: {\n    // Stale-While-Revalidate (SWR) : cache serveur intelligent\n    '/blog/**': { swr: 3600 },\n    // Cache statique brut pour les assets stricts\n    '/assets/**': { headers: { 'cache-control': 's-maxage=31536000' } }\n  }\n})\n","typescript",[99,1078,1079,1094,1104,1109,1127,1136,1149,1169,1174,1179,1183,1192,1197,1223,1228,1270,1274],{"__ignoreMap":109},[113,1080,1081,1084,1087,1090,1092],{"class":115,"line":116},[113,1082,1083],{"class":975},"export",[113,1085,1086],{"class":975}," default",[113,1088,1089],{"class":878}," defineNuxtConfig",[113,1091,882],{"class":384},[113,1093,993],{"class":119},[113,1095,1096,1099,1102],{"class":115,"line":130},[113,1097,1098],{"class":123},"  nitro",[113,1100,1101],{"class":119},":",[113,1103,891],{"class":119},[113,1105,1106],{"class":115,"line":142},[113,1107,1108],{"class":633},"    // Active le déploiement mondial sur Vercel Edge\n",[113,1110,1111,1114,1116,1119,1122,1124],{"class":115,"line":163},[113,1112,1113],{"class":123},"    preset",[113,1115,1101],{"class":119},[113,1117,1118],{"class":119}," '",[113,1120,1121],{"class":155},"vercel",[113,1123,953],{"class":119},[113,1125,1126],{"class":119},",\n",[113,1128,1129,1132,1134],{"class":115,"line":179},[113,1130,1131],{"class":123},"    prerender",[113,1133,1101],{"class":119},[113,1135,891],{"class":119},[113,1137,1138,1141,1143,1147],{"class":115,"line":196},[113,1139,1140],{"class":123},"      crawlLinks",[113,1142,1101],{"class":119},[113,1144,1146],{"class":1145},"sfNiH"," true",[113,1148,1126],{"class":119},[113,1150,1151,1154,1156,1159,1161,1164,1166],{"class":115,"line":213},[113,1152,1153],{"class":123},"      routes",[113,1155,1101],{"class":119},[113,1157,1158],{"class":384}," [",[113,1160,953],{"class":119},[113,1162,1163],{"class":155},"/",[113,1165,953],{"class":119},[113,1167,1168],{"class":384},"]\n",[113,1170,1171],{"class":115,"line":230},[113,1172,1173],{"class":119},"    }\n",[113,1175,1176],{"class":115,"line":245},[113,1177,1178],{"class":119},"  },\n",[113,1180,1181],{"class":115,"line":260},[113,1182,927],{"class":384},[113,1184,1185,1188,1190],{"class":115,"line":266},[113,1186,1187],{"class":123},"  routeRules",[113,1189,1101],{"class":119},[113,1191,891],{"class":119},[113,1193,1194],{"class":115,"line":272},[113,1195,1196],{"class":633},"    // Stale-While-Revalidate (SWR) : cache serveur intelligent\n",[113,1198,1199,1202,1205,1207,1209,1212,1215,1217,1220],{"class":115,"line":1011},[113,1200,1201],{"class":119},"    '",[113,1203,1204],{"class":123},"/blog/**",[113,1206,953],{"class":119},[113,1208,1101],{"class":119},[113,1210,1211],{"class":119}," {",[113,1213,1214],{"class":123}," swr",[113,1216,1101],{"class":119},[113,1218,1219],{"class":921}," 3600",[113,1221,1222],{"class":119}," },\n",[113,1224,1225],{"class":115,"line":1016},[113,1226,1227],{"class":633},"    // Cache statique brut pour les assets stricts\n",[113,1229,1230,1232,1235,1237,1239,1241,1244,1246,1248,1250,1253,1255,1257,1259,1262,1264,1267],{"class":115,"line":1022},[113,1231,1201],{"class":119},[113,1233,1234],{"class":123},"/assets/**",[113,1236,953],{"class":119},[113,1238,1101],{"class":119},[113,1240,1211],{"class":119},[113,1242,1243],{"class":123}," headers",[113,1245,1101],{"class":119},[113,1247,1211],{"class":119},[113,1249,1118],{"class":119},[113,1251,1252],{"class":123},"cache-control",[113,1254,953],{"class":119},[113,1256,1101],{"class":119},[113,1258,1118],{"class":119},[113,1260,1261],{"class":155},"s-maxage=31536000",[113,1263,953],{"class":119},[113,1265,1266],{"class":119}," }",[113,1268,1269],{"class":119}," }\n",[113,1271,1272],{"class":115,"line":1031},[113,1273,1008],{"class":119},[113,1275,1277,1279],{"class":115,"line":1276},17,[113,1278,1025],{"class":119},[113,1280,1028],{"class":384},[10,1282,1283,1284,1287],{},"La règle ",[99,1285,1286],{},"swr: 3600"," informe les serveurs Edge de Vercel : \"Mets en cache cette page côté serveur pendant 1 heure. Si un utilisateur la demande, fournis-la instantanément (en quelques millisecondes). En coulisse, re-génère là de façon asynchrone si elle n'est plus à jour.\"",[10,1289,1290],{},"Le visiteur ne subit jamais le temps de génération.",[10,1292,1293,1294,21],{},"Pour en savoir plus sur les raisons profondes de ce choix technologique qui bouleverse le secteur, je vous invite à lire mon essai détaillé : ",[1295,1296,1298],"a",{"href":1297},"/blog/pourquoi-nuxt","Pourquoi j'ai choisi Nuxt 4",[33,1300,1302],{"id":1301},"le-résultat-un-100100-en-production","Le résultat : un 100/100 en production",[10,1304,1305],{},"Appliquer ces principes sur chaque projet donne des résultats concrets. Plus d'interface qui rame, plus de layout qui saute au chargement, plus de temps d'attente sur mobile.",[10,1307,1308],{},"Voici le résultat d'un audit de production récent : un Lighthouse à 100/100, sur PC comme sur mobile.",[10,1310,1311],{},[1312,1313],"img",{"alt":1314,"src":1315},"Screenshot réel du 100/100 sur Lighthouse","/blog/lighthouse.png",[10,1317,1318],{},"Atteindre ces métriques demande du soin, de la méthode et une bonne connaissance de l'écosystème Vue.js. C'est le standard que j'applique à chaque projet.",[1320,1321],"hr",{},[10,1323,1324,1327,1330,1331,1333],{},[14,1325,1326],{},"Votre site est lent et vos conversions stagnent ?",[1328,1329],"br",{},"\nUn audit performance peut révéler des gains rapides. Je peux analyser votre application et vous proposer un plan d'optimisation concret.",[1328,1332],{},[1295,1334,1336],{"href":1335},"/contact","Demander un audit gratuit →",[1338,1339,1340],"style",{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-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 .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 pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}",{"title":109,"searchDepth":130,"depth":130,"links":1342},[1343,1344,1348,1351,1356,1357],{"id":35,"depth":130,"text":36},{"id":81,"depth":130,"text":82,"children":1345},[1346,1347],{"id":86,"depth":142,"text":87},{"id":306,"depth":142,"text":307},{"id":317,"depth":130,"text":318,"children":1349},[1350],{"id":321,"depth":142,"text":322},{"id":581,"depth":130,"text":582,"children":1352},[1353,1354,1355],{"id":588,"depth":142,"text":589},{"id":732,"depth":142,"text":733},{"id":849,"depth":142,"text":850},{"id":1050,"depth":130,"text":1051},{"id":1301,"depth":130,"text":1302},"Frontend","Découvrez mon guide technique de type 'Pilier' pour maîtriser les Core Web Vitals en 2026. Apprenez à optimiser LCP, INP et CLS avec Nuxt 4.","md",{"src":1315,"alt":1362},"Screenshot réel d'un Lighthouse à 100/100",{},"/blog/lighthouse","2026-02-23",{"title":5,"description":1359},{"loc":1364},"blog/lighthouse","Guide technique pour atteindre 100/100 sur Lighthouse avec Nuxt 4, couvrant l'optimisation des images, le rendu hybride et les performances sur Vercel.",null,"YJyLfREQlrqLtH2EZ5KfSlDjnzXavVOb7lsB3UpSsC0",[1373,1377],{"title":1374,"path":1375,"stem":1376},"Combien coûte un site web en 2026 ? Guide complet des tarifs","/blog/combien-coute-site-web","blog/combien-coute-site-web",{"title":1378,"path":1379,"stem":1380},"Moltbook : un Reddit pour agents IA, fascinant mais pas sans risques","/blog/moltbook","blog/moltbook",1775571493194]