[{"data":1,"prerenderedAt":1360},["ShallowReactive",2],{"blog-post-/blog/three-js-and-image-optimization-nuxt/":3},{"id":4,"title":5,"body":6,"description":1332,"extension":1333,"meta":1334,"navigation":1353,"path":1355,"seo":1356,"sitemap":1357,"stem":1358,"__hash__":1359},"content/blog/three-js-and-image-optimization-nuxt.md","Three.js and Image Optimization in Nuxt - A Practical Guide",{"type":7,"value":8,"toc":1322},"minimark",[9,14,18,23,52,55,58,62,65,288,317,320,322,326,329,458,469,471,475,478,591,610,625,627,631,634,772,805,812,814,818,821,929,946,963,965,969,976,1118,1121,1205,1225,1227,1231,1308,1318],[10,11,13],"h1",{"id":12},"threejs-and-image-optimization-in-nuxt","Three.js and Image Optimization in Nuxt",[15,16,17],"p",{},"Combining Three.js with a content-heavy site can hurt Core Web Vitals if you don't optimize. Here's a practical approach we use: defer WebGL, split the Three.js bundle, and serve images in modern formats with the right loading strategy.",[19,20,22],"h2",{"id":21},"the-challenge","The Challenge",[24,25,26,42],"ul",{},[27,28,29,33,34,37,38,41],"li",{},[30,31,32],"strong",{},"Three.js"," is large and can block the main thread, hurting ",[30,35,36],{},"First Input Delay (FID)"," and ",[30,39,40],{},"Total Blocking Time (TBT)",".",[27,43,44,47,48,51],{},[30,45,46],{},"Hero and gallery images"," can blow up ",[30,49,50],{},"Largest Contentful Paint (LCP)"," and bandwidth if they're not sized, lazy-loaded, or converted to WebP/AVIF.",[15,53,54],{},"We wanted a starfield background (Three.js) and a big hero image without sacrificing a 99 Lighthouse performance score. These are the changes that made it possible.",[56,57],"hr",{},[19,59,61],{"id":60},"_1-defer-threejs-until-the-browser-is-idle","1. Defer Three.js Until the Browser Is Idle",[15,63,64],{},"Initializing WebGL on mount can create a long task and block user input. We delay setup until the browser is idle (or after a short timeout).",[66,67,72],"pre",{"className":68,"code":69,"language":70,"meta":71,"style":71},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","// components/StarryBackground.vue\nonMounted(() => {\n  const start = () => {\n    initStars();\n    window.addEventListener(\"resize\", handleResize);\n  };\n  if (typeof requestIdleCallback !== \"undefined\") {\n    requestIdleCallback(start, { timeout: 2000 });\n  } else {\n    setTimeout(start, 0);\n  }\n});\n","ts","",[73,74,75,84,106,125,137,170,176,209,242,253,272,278],"code",{"__ignoreMap":71},[76,77,80],"span",{"class":78,"line":79},"line",1,[76,81,83],{"class":82},"sHwdD","// components/StarryBackground.vue\n",[76,85,87,91,95,99,103],{"class":78,"line":86},2,[76,88,90],{"class":89},"s2Zo4","onMounted",[76,92,94],{"class":93},"sTEyZ","(",[76,96,98],{"class":97},"sMK4o","()",[76,100,102],{"class":101},"spNyl"," =>",[76,104,105],{"class":97}," {\n",[76,107,109,112,115,118,121,123],{"class":78,"line":108},3,[76,110,111],{"class":101},"  const",[76,113,114],{"class":93}," start",[76,116,117],{"class":97}," =",[76,119,120],{"class":97}," ()",[76,122,102],{"class":101},[76,124,105],{"class":97},[76,126,128,131,134],{"class":78,"line":127},4,[76,129,130],{"class":89},"    initStars",[76,132,98],{"class":133},"swJcz",[76,135,136],{"class":97},";\n",[76,138,140,143,145,148,150,153,157,159,162,165,168],{"class":78,"line":139},5,[76,141,142],{"class":93},"    window",[76,144,41],{"class":97},[76,146,147],{"class":89},"addEventListener",[76,149,94],{"class":133},[76,151,152],{"class":97},"\"",[76,154,156],{"class":155},"sfazB","resize",[76,158,152],{"class":97},[76,160,161],{"class":97},",",[76,163,164],{"class":93}," handleResize",[76,166,167],{"class":133},")",[76,169,136],{"class":97},[76,171,173],{"class":78,"line":172},6,[76,174,175],{"class":97},"  };\n",[76,177,179,183,186,189,192,195,198,201,203,206],{"class":78,"line":178},7,[76,180,182],{"class":181},"s7zQu","  if",[76,184,185],{"class":133}," (",[76,187,188],{"class":97},"typeof",[76,190,191],{"class":93}," requestIdleCallback",[76,193,194],{"class":97}," !==",[76,196,197],{"class":97}," \"",[76,199,200],{"class":155},"undefined",[76,202,152],{"class":97},[76,204,205],{"class":133},") ",[76,207,208],{"class":97},"{\n",[76,210,212,215,217,220,222,225,228,231,235,238,240],{"class":78,"line":211},8,[76,213,214],{"class":89},"    requestIdleCallback",[76,216,94],{"class":133},[76,218,219],{"class":93},"start",[76,221,161],{"class":97},[76,223,224],{"class":97}," {",[76,226,227],{"class":133}," timeout",[76,229,230],{"class":97},":",[76,232,234],{"class":233},"sbssI"," 2000",[76,236,237],{"class":97}," }",[76,239,167],{"class":133},[76,241,136],{"class":97},[76,243,245,248,251],{"class":78,"line":244},9,[76,246,247],{"class":97},"  }",[76,249,250],{"class":181}," else",[76,252,105],{"class":97},[76,254,256,259,261,263,265,268,270],{"class":78,"line":255},10,[76,257,258],{"class":89},"    setTimeout",[76,260,94],{"class":133},[76,262,219],{"class":93},[76,264,161],{"class":97},[76,266,267],{"class":233}," 0",[76,269,167],{"class":133},[76,271,136],{"class":97},[76,273,275],{"class":78,"line":274},11,[76,276,277],{"class":97},"  }\n",[76,279,281,284,286],{"class":78,"line":280},12,[76,282,283],{"class":97},"}",[76,285,167],{"class":93},[76,287,136],{"class":97},[24,289,290,298,306],{},[27,291,292,297],{},[30,293,294],{},[73,295,296],{},"requestIdleCallback"," runs when the main thread is free, so the first paint and critical JS aren't delayed.",[27,299,300,305],{},[30,301,302],{},[73,303,304],{},"timeout: 2000"," ensures we still run after 2s if the browser never reports idle.",[27,307,308,314,315,41],{},[30,309,310,311],{},"Fallback ",[73,312,313],{},"setTimeout(start, 0)"," for environments that don't support ",[73,316,296],{},[15,318,319],{},"Result: Three.js no longer sits on the critical path; LCP and FID stay good.",[56,321],{},[19,323,325],{"id":324},"_2-put-threejs-in-its-own-chunk","2. Put Three.js in Its Own Chunk",[15,327,328],{},"Bundling Three.js with the main app bundle would make the initial JS payload huge. We split it so it loads in parallel and doesn't block parsing.",[66,330,332],{"className":68,"code":331,"language":70,"meta":71,"style":71},"// nuxt.config.ts\nvite: {\n  build: {\n    rollupOptions: {\n      output: {\n        manualChunks: (id) => {\n          if (id.includes(\"node_modules/three\")) return \"three\";\n        },\n      },\n    },\n  },\n},\n",[73,333,334,339,349,358,367,376,395,433,438,443,448,453],{"__ignoreMap":71},[76,335,336],{"class":78,"line":79},[76,337,338],{"class":82},"// nuxt.config.ts\n",[76,340,341,345,347],{"class":78,"line":86},[76,342,344],{"class":343},"sBMFI","vite",[76,346,230],{"class":97},[76,348,105],{"class":97},[76,350,351,354,356],{"class":78,"line":108},[76,352,353],{"class":343},"  build",[76,355,230],{"class":97},[76,357,105],{"class":97},[76,359,360,363,365],{"class":78,"line":127},[76,361,362],{"class":343},"    rollupOptions",[76,364,230],{"class":97},[76,366,105],{"class":97},[76,368,369,372,374],{"class":78,"line":139},[76,370,371],{"class":343},"      output",[76,373,230],{"class":97},[76,375,105],{"class":97},[76,377,378,381,383,385,389,391,393],{"class":78,"line":172},[76,379,380],{"class":343},"        manualChunks",[76,382,230],{"class":97},[76,384,185],{"class":97},[76,386,388],{"class":387},"sHdIc","id",[76,390,167],{"class":97},[76,392,102],{"class":101},[76,394,105],{"class":97},[76,396,397,400,402,404,406,409,411,413,416,418,421,424,426,429,431],{"class":78,"line":178},[76,398,399],{"class":181},"          if",[76,401,185],{"class":133},[76,403,388],{"class":93},[76,405,41],{"class":97},[76,407,408],{"class":89},"includes",[76,410,94],{"class":133},[76,412,152],{"class":97},[76,414,415],{"class":155},"node_modules/three",[76,417,152],{"class":97},[76,419,420],{"class":133},")) ",[76,422,423],{"class":181},"return",[76,425,197],{"class":97},[76,427,428],{"class":155},"three",[76,430,152],{"class":97},[76,432,136],{"class":97},[76,434,435],{"class":78,"line":211},[76,436,437],{"class":97},"        },\n",[76,439,440],{"class":78,"line":244},[76,441,442],{"class":97},"      },\n",[76,444,445],{"class":78,"line":255},[76,446,447],{"class":97},"    },\n",[76,449,450],{"class":78,"line":274},[76,451,452],{"class":97},"  },\n",[76,454,455],{"class":78,"line":280},[76,456,457],{"class":97},"},\n",[24,459,460,466],{},[27,461,462,463,465],{},"The ",[30,464,428],{}," chunk loads when the component that imports it is needed.",[27,467,468],{},"Main bundle stays smaller; Three.js is cached separately and doesn't block first load.",[56,470],{},[19,472,474],{"id":473},"_3-webgl-and-renderer-tweaks","3. WebGL and Renderer Tweaks",[15,476,477],{},"Small renderer settings that help performance and battery:",[66,479,481],{"className":68,"code":480,"language":70,"meta":71,"style":71},"renderer = new THREE.WebGLRenderer({\n  antialias: true,\n  alpha: true,\n  powerPreference: \"high-performance\",\n});\nrenderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\n",[73,482,483,506,520,531,547,555],{"__ignoreMap":71},[76,484,485,488,491,494,497,499,502,504],{"class":78,"line":79},[76,486,487],{"class":93},"renderer ",[76,489,490],{"class":97},"=",[76,492,493],{"class":97}," new",[76,495,496],{"class":93}," THREE",[76,498,41],{"class":97},[76,500,501],{"class":89},"WebGLRenderer",[76,503,94],{"class":93},[76,505,208],{"class":97},[76,507,508,511,513,517],{"class":78,"line":86},[76,509,510],{"class":133},"  antialias",[76,512,230],{"class":97},[76,514,516],{"class":515},"sfNiH"," true",[76,518,519],{"class":97},",\n",[76,521,522,525,527,529],{"class":78,"line":108},[76,523,524],{"class":133},"  alpha",[76,526,230],{"class":97},[76,528,516],{"class":515},[76,530,519],{"class":97},[76,532,533,536,538,540,543,545],{"class":78,"line":127},[76,534,535],{"class":133},"  powerPreference",[76,537,230],{"class":97},[76,539,197],{"class":97},[76,541,542],{"class":155},"high-performance",[76,544,152],{"class":97},[76,546,519],{"class":97},[76,548,549,551,553],{"class":78,"line":139},[76,550,283],{"class":97},[76,552,167],{"class":93},[76,554,136],{"class":97},[76,556,557,560,562,565,568,570,573,576,578,581,583,586,589],{"class":78,"line":172},[76,558,559],{"class":93},"renderer",[76,561,41],{"class":97},[76,563,564],{"class":89},"setPixelRatio",[76,566,567],{"class":93},"(Math",[76,569,41],{"class":97},[76,571,572],{"class":89},"min",[76,574,575],{"class":93},"(window",[76,577,41],{"class":97},[76,579,580],{"class":93},"devicePixelRatio",[76,582,161],{"class":97},[76,584,585],{"class":233}," 2",[76,587,588],{"class":93},"))",[76,590,136],{"class":97},[24,592,593,601],{},[27,594,595,600],{},[30,596,597],{},[73,598,599],{},"powerPreference: \"high-performance\""," hints the GPU to prefer performance (where supported).",[27,602,603,609],{},[30,604,605,606,608],{},"Capping ",[73,607,580],{}," at 2"," avoids rendering at 3x on high-DPI screens, which can be expensive for particle-heavy scenes.",[15,611,612,613,616,617,620,621,624],{},"We also ",[30,614,615],{},"debounce resize"," (e.g. 100ms) and ",[30,618,619],{},"dispose"," geometry, material, texture, and renderer in ",[73,622,623],{},"onUnmounted"," to avoid leaks and extra work.",[56,626],{},[19,628,630],{"id":629},"_4-hero-image-above-the-fold-priority","4. Hero Image: Above-the-Fold Priority",[15,632,633],{},"The hero image is LCP candidate, so we treat it as critical:",[66,635,639],{"className":636,"code":637,"language":638,"meta":71,"style":71},"language-vue shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003CNuxtImg\n  src=\"/images/avatar.png\"\n  alt=\"WonderCoder Avatar\"\n  class=\"hero-bg-image w-full h-full object-cover lg:object-contain lg:object-right\"\n  format=\"webp\"\n  quality=\"80\"\n  sizes=\"sm:100vw md:100vw lg:60vw\"\n  preload\n  fetchpriority=\"high\"\n  loading=\"eager\"\n/>\n","vue",[73,640,641,649,664,678,692,706,720,734,739,753,767],{"__ignoreMap":71},[76,642,643,646],{"class":78,"line":79},[76,644,645],{"class":97},"\u003C",[76,647,648],{"class":133},"NuxtImg\n",[76,650,651,654,656,658,661],{"class":78,"line":86},[76,652,653],{"class":101},"  src",[76,655,490],{"class":97},[76,657,152],{"class":97},[76,659,660],{"class":155},"/images/avatar.png",[76,662,663],{"class":97},"\"\n",[76,665,666,669,671,673,676],{"class":78,"line":108},[76,667,668],{"class":101},"  alt",[76,670,490],{"class":97},[76,672,152],{"class":97},[76,674,675],{"class":155},"WonderCoder Avatar",[76,677,663],{"class":97},[76,679,680,683,685,687,690],{"class":78,"line":127},[76,681,682],{"class":101},"  class",[76,684,490],{"class":97},[76,686,152],{"class":97},[76,688,689],{"class":155},"hero-bg-image w-full h-full object-cover lg:object-contain lg:object-right",[76,691,663],{"class":97},[76,693,694,697,699,701,704],{"class":78,"line":139},[76,695,696],{"class":101},"  format",[76,698,490],{"class":97},[76,700,152],{"class":97},[76,702,703],{"class":155},"webp",[76,705,663],{"class":97},[76,707,708,711,713,715,718],{"class":78,"line":172},[76,709,710],{"class":101},"  quality",[76,712,490],{"class":97},[76,714,152],{"class":97},[76,716,717],{"class":155},"80",[76,719,663],{"class":97},[76,721,722,725,727,729,732],{"class":78,"line":178},[76,723,724],{"class":101},"  sizes",[76,726,490],{"class":97},[76,728,152],{"class":97},[76,730,731],{"class":155},"sm:100vw md:100vw lg:60vw",[76,733,663],{"class":97},[76,735,736],{"class":78,"line":211},[76,737,738],{"class":101},"  preload\n",[76,740,741,744,746,748,751],{"class":78,"line":244},[76,742,743],{"class":101},"  fetchpriority",[76,745,490],{"class":97},[76,747,152],{"class":97},[76,749,750],{"class":155},"high",[76,752,663],{"class":97},[76,754,755,758,760,762,765],{"class":78,"line":255},[76,756,757],{"class":101},"  loading",[76,759,490],{"class":97},[76,761,152],{"class":97},[76,763,764],{"class":155},"eager",[76,766,663],{"class":97},[76,768,769],{"class":78,"line":274},[76,770,771],{"class":93},"/>\n",[24,773,774,782,790],{},[27,775,776,781],{},[30,777,778],{},[73,779,780],{},"format=\"webp\""," (and AVIF in config) keeps size down.",[27,783,784,789],{},[30,785,786],{},[73,787,788],{},"sizes"," ensures the right width is requested per viewport.",[27,791,792,804],{},[30,793,794,797,798,797,801],{},[73,795,796],{},"preload"," + ",[73,799,800],{},"fetchpriority=\"high\"",[73,802,803],{},"loading=\"eager\""," make the browser fetch and decode the hero image early so LCP is fast.",[15,806,807,808,811],{},"We combine this with responsive CSS (e.g. ",[73,809,810],{},"object-fit",", mask, and max width) so the image doesn't overflow and layout stays stable.",[56,813],{},[19,815,817],{"id":816},"_5-below-the-fold-lazy-and-responsive","5. Below-the-Fold: Lazy and Responsive",[15,819,820],{},"Cards and lists use lazy loading and responsive sizes so we don't load full-size images for thumbnails:",[66,822,824],{"className":636,"code":823,"language":638,"meta":71,"style":71},"\u003CNuxtImg\n  :src=\"project.image\"\n  :alt=\"project.name\"\n  class=\"w-full h-full object-cover group-hover:scale-110 transition-transform duration-500\"\n  loading=\"lazy\"\n  format=\"webp\"\n  sizes=\"(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw\"\n/>\n",[73,825,826,832,854,874,887,900,912,925],{"__ignoreMap":71},[76,827,828,830],{"class":78,"line":79},[76,829,645],{"class":97},[76,831,648],{"class":133},[76,833,834,837,840,842,844,847,849,852],{"class":78,"line":86},[76,835,836],{"class":97},"  :",[76,838,839],{"class":101},"src",[76,841,490],{"class":97},[76,843,152],{"class":97},[76,845,846],{"class":93},"project",[76,848,41],{"class":97},[76,850,851],{"class":93},"image",[76,853,663],{"class":97},[76,855,856,858,861,863,865,867,869,872],{"class":78,"line":108},[76,857,836],{"class":97},[76,859,860],{"class":101},"alt",[76,862,490],{"class":97},[76,864,152],{"class":97},[76,866,846],{"class":93},[76,868,41],{"class":97},[76,870,871],{"class":93},"name",[76,873,663],{"class":97},[76,875,876,878,880,882,885],{"class":78,"line":127},[76,877,682],{"class":101},[76,879,490],{"class":97},[76,881,152],{"class":97},[76,883,884],{"class":155},"w-full h-full object-cover group-hover:scale-110 transition-transform duration-500",[76,886,663],{"class":97},[76,888,889,891,893,895,898],{"class":78,"line":139},[76,890,757],{"class":101},[76,892,490],{"class":97},[76,894,152],{"class":97},[76,896,897],{"class":155},"lazy",[76,899,663],{"class":97},[76,901,902,904,906,908,910],{"class":78,"line":172},[76,903,696],{"class":101},[76,905,490],{"class":97},[76,907,152],{"class":97},[76,909,703],{"class":155},[76,911,663],{"class":97},[76,913,914,916,918,920,923],{"class":78,"line":178},[76,915,724],{"class":101},[76,917,490],{"class":97},[76,919,152],{"class":97},[76,921,922],{"class":155},"(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw",[76,924,663],{"class":97},[76,926,927],{"class":78,"line":211},[76,928,771],{"class":93},[24,930,931,939],{},[27,932,933,938],{},[30,934,935],{},[73,936,937],{},"loading=\"lazy\""," defers load until near the viewport.",[27,940,941,945],{},[30,942,943],{},[73,944,788],{}," matches layout (e.g. 1 column on mobile, 2 on tablet, 3 on desktop) so the server (or Nuxt Image) can serve an appropriately sized file.",[15,947,948,949,952,953,956,957,960,961,41],{},"Same idea applies to ",[30,950,951],{},"product cards",", ",[30,954,955],{},"blog cards",", and ",[30,958,959],{},"carousels",": lazy + webp + sensible ",[73,962,788],{},[56,964],{},[19,966,968],{"id":967},"_6-global-image-and-cache-settings","6. Global Image and Cache Settings",[15,970,971,972,975],{},"In ",[73,973,974],{},"nuxt.config.ts"," we set defaults and long-lived cache for assets:",[66,977,979],{"className":68,"code":978,"language":70,"meta":71,"style":71},"image: {\n  domains: [\"img.youtube.com\"],\n  format: [\"avif\", \"webp\"],\n  quality: 75,\n  screens: { sm: 640, md: 768, lg: 1024, xl: 1280, \"2xl\": 1536 },\n},\n",[73,980,981,989,1011,1038,1049,1114],{"__ignoreMap":71},[76,982,983,985,987],{"class":78,"line":79},[76,984,851],{"class":343},[76,986,230],{"class":97},[76,988,105],{"class":97},[76,990,991,994,996,999,1001,1004,1006,1009],{"class":78,"line":86},[76,992,993],{"class":343},"  domains",[76,995,230],{"class":97},[76,997,998],{"class":133}," [",[76,1000,152],{"class":97},[76,1002,1003],{"class":155},"img.youtube.com",[76,1005,152],{"class":97},[76,1007,1008],{"class":133},"]",[76,1010,519],{"class":97},[76,1012,1013,1015,1017,1019,1021,1024,1026,1028,1030,1032,1034,1036],{"class":78,"line":108},[76,1014,696],{"class":343},[76,1016,230],{"class":97},[76,1018,998],{"class":133},[76,1020,152],{"class":97},[76,1022,1023],{"class":155},"avif",[76,1025,152],{"class":97},[76,1027,161],{"class":97},[76,1029,197],{"class":97},[76,1031,703],{"class":155},[76,1033,152],{"class":97},[76,1035,1008],{"class":133},[76,1037,519],{"class":97},[76,1039,1040,1042,1044,1047],{"class":78,"line":127},[76,1041,710],{"class":343},[76,1043,230],{"class":97},[76,1045,1046],{"class":233}," 75",[76,1048,519],{"class":97},[76,1050,1051,1054,1056,1058,1061,1063,1066,1068,1071,1073,1076,1078,1081,1083,1086,1088,1091,1093,1096,1098,1100,1103,1105,1108,1111],{"class":78,"line":139},[76,1052,1053],{"class":343},"  screens",[76,1055,230],{"class":97},[76,1057,224],{"class":97},[76,1059,1060],{"class":343}," sm",[76,1062,230],{"class":97},[76,1064,1065],{"class":233}," 640",[76,1067,161],{"class":97},[76,1069,1070],{"class":343}," md",[76,1072,230],{"class":97},[76,1074,1075],{"class":233}," 768",[76,1077,161],{"class":97},[76,1079,1080],{"class":343}," lg",[76,1082,230],{"class":97},[76,1084,1085],{"class":233}," 1024",[76,1087,161],{"class":97},[76,1089,1090],{"class":343}," xl",[76,1092,230],{"class":97},[76,1094,1095],{"class":233}," 1280",[76,1097,161],{"class":97},[76,1099,197],{"class":97},[76,1101,1102],{"class":155},"2xl",[76,1104,152],{"class":97},[76,1106,1107],{"class":133},": ",[76,1109,1110],{"class":233},"1536",[76,1112,1113],{"class":97}," },\n",[76,1115,1116],{"class":78,"line":172},[76,1117,457],{"class":97},[15,1119,1120],{},"And in Nitro route rules:",[66,1122,1124],{"className":68,"code":1123,"language":70,"meta":71,"style":71},"\"/_nuxt/**\": { headers: { \"Cache-Control\": \"public, max-age=31536000, immutable\" } },\n\"/images/**\": { headers: { \"Cache-Control\": \"public, max-age=31536000\" } },\n",[73,1125,1126,1167],{"__ignoreMap":71},[76,1127,1128,1130,1133,1135,1137,1140,1143,1145,1147,1149,1152,1154,1156,1158,1161,1163,1165],{"class":78,"line":79},[76,1129,152],{"class":97},[76,1131,1132],{"class":155},"/_nuxt/**",[76,1134,152],{"class":97},[76,1136,1107],{"class":93},[76,1138,1139],{"class":97},"{",[76,1141,1142],{"class":133}," headers",[76,1144,230],{"class":97},[76,1146,224],{"class":97},[76,1148,197],{"class":97},[76,1150,1151],{"class":133},"Cache-Control",[76,1153,152],{"class":97},[76,1155,230],{"class":97},[76,1157,197],{"class":97},[76,1159,1160],{"class":155},"public, max-age=31536000, immutable",[76,1162,152],{"class":97},[76,1164,237],{"class":97},[76,1166,1113],{"class":97},[76,1168,1169,1171,1174,1176,1178,1180,1182,1184,1186,1188,1190,1192,1194,1196,1199,1201,1203],{"class":78,"line":86},[76,1170,152],{"class":97},[76,1172,1173],{"class":155},"/images/**",[76,1175,152],{"class":97},[76,1177,1107],{"class":93},[76,1179,1139],{"class":97},[76,1181,1142],{"class":133},[76,1183,230],{"class":97},[76,1185,224],{"class":97},[76,1187,197],{"class":97},[76,1189,1151],{"class":133},[76,1191,152],{"class":97},[76,1193,230],{"class":97},[76,1195,197],{"class":97},[76,1197,1198],{"class":155},"public, max-age=31536000",[76,1200,152],{"class":97},[76,1202,237],{"class":97},[76,1204,1113],{"class":97},[24,1206,1207,1216],{},[27,1208,1209,37,1212,1215],{},[30,1210,1211],{},"AVIF/WebP",[30,1213,1214],{},"quality 75"," keep a good balance between size and visual quality.",[27,1217,1218,1221,1222,1224],{},[30,1219,1220],{},"Immutable"," for hashed JS/CSS and long cache for ",[73,1223,1173],{}," improve repeat visits.",[56,1226],{},[19,1228,1230],{"id":1229},"summary","Summary",[1232,1233,1234,1247],"table",{},[1235,1236,1237],"thead",{},[1238,1239,1240,1244],"tr",{},[1241,1242,1243],"th",{},"Area",[1241,1245,1246],{},"What we did",[1248,1249,1250,1263,1281,1298],"tbody",{},[1238,1251,1252,1257],{},[1253,1254,1255],"td",{},[30,1256,32],{},[1253,1258,1259,1260,1262],{},"Defer init with ",[73,1261,296],{},"; put Three in a separate chunk; cap pixel ratio; dispose on unmount.",[1238,1264,1265,1270],{},[1253,1266,1267],{},[30,1268,1269],{},"Hero image",[1253,1271,1272,1273,952,1275,952,1277,952,1279,41],{},"NuxtImg with WebP, ",[73,1274,788],{},[73,1276,796],{},[73,1278,800],{},[73,1280,803],{},[1238,1282,1283,1288],{},[1253,1284,1285],{},[30,1286,1287],{},"Cards / lists",[1253,1289,1290,1291,952,1293,1295,1296,41],{},"NuxtImg with ",[73,1292,937],{},[73,1294,780],{},", and layout-matched ",[73,1297,788],{},[1238,1299,1300,1305],{},[1253,1301,1302],{},[30,1303,1304],{},"Global",[1253,1306,1307],{},"Nuxt Image config (formats, quality, screens) and strong cache headers for static assets.",[15,1309,1310,1311,1314,1315,1317],{},"With this, we keep a ",[30,1312,1313],{},"99 Lighthouse performance score"," while using a Three.js starfield and large imagery. You can reuse the same patterns: idle deferral for heavy JS, code splitting for Three.js, and NuxtImg with the right priority and ",[73,1316,788],{}," for each image.",[1319,1320,1321],"style",{},"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 .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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 .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}",{"title":71,"searchDepth":86,"depth":86,"links":1323},[1324,1325,1326,1327,1328,1329,1330,1331],{"id":21,"depth":86,"text":22},{"id":60,"depth":86,"text":61},{"id":324,"depth":86,"text":325},{"id":473,"depth":86,"text":474},{"id":629,"depth":86,"text":630},{"id":816,"depth":86,"text":817},{"id":967,"depth":86,"text":968},{"id":1229,"depth":86,"text":1230},"How to use Three.js in Nuxt without killing your Lighthouse score. Covers lazy-loading WebGL, code splitting Three.js bundles, NuxtImg optimization, and Core Web Vitals tuning.","md",{"author":1335,"date":1336,"image":1337,"category":1338,"tags":1339,"featured":1353,"draft":1354},"WonderCoder Team","2025-01-31T10:00:00.000Z","/images/projects/starrynight.png","Performance",[1340,1341,1342,1343,1344,1345,1346,1347,1348,1349,1350,1351,1352],"three-js","nuxt","performance","webgl","image-optimization","threejs-nuxt-tutorial","threejs-performance","nuxt-image-optimization","lighthouse-score-threejs","core-web-vitals-threejs","how-to-use-threejs-nuxt","lazy-load-threejs","webgl-performance-nuxt",true,false,"/blog/three-js-and-image-optimization-nuxt",{"title":5,"description":1332},{"loc":1355},"blog/three-js-and-image-optimization-nuxt","GIS-OHeW_TZtlOFNbS2zFW6_fasQnRN-gno5WFtagqo",1782986782786]