[{"data":1,"prerenderedAt":2121},["ShallowReactive",2],{"content:\u002Fjavascript-bundle-optimization-code-splitting\u002Fvite-and-rollup-build-optimization":3,"surroundings:\u002Fjavascript-bundle-optimization-code-splitting\u002Fvite-and-rollup-build-optimization":2112},{"id":4,"title":5,"body":6,"description":2093,"extension":2094,"meta":2095,"navigation":432,"path":2106,"seo":2107,"stem":2110,"__hash__":2111},"content\u002Fjavascript-bundle-optimization-code-splitting\u002Fvite-and-rollup-build-optimization\u002Findex.md","Vite & Rollup Build Optimization",{"type":7,"value":8,"toc":2076},"minimark",[9,14,24,32,185,190,193,219,233,318,322,367,371,374,569,573,583,864,868,871,991,995,998,1043,1047,1050,1132,1240,1252,1256,1266,1274,1284,1297,1306,1310,1318,1634,1637,1686,1690,1699,1718,1868,1872,1887,1891,1897,1918,1922,1937,1950,1970,1979,1996,2000,2027,2031,2061,2066,2069,2072],[10,11,13],"h1",{"id":12},"vite-rollup-build-optimization-shaping-what-ships-to-the-browser","Vite & Rollup Build Optimization: Shaping What Ships to the Browser",[15,16,17,18,23],"p",{},"This topic extends ",[19,20,22],"a",{"href":21},"\u002Fjavascript-bundle-optimization-code-splitting\u002F","JavaScript Bundle Optimization & Code Splitting"," to the build tool that now produces a large share of production frontends. Vite's development server serves native ES modules, but its production build is a Rollup bundle (with Rolldown increasingly replacing Rollup underneath in recent versions), and that build decides how many requests the browser makes, how much JavaScript it parses before the first interaction, and how well the output caches across deploys.",[15,25,26,27,31],{},"Vite's defaults are good — tree-shaking, route-level splitting from dynamic imports, CSS code splitting, ",[28,29,30],"code",{},"modulepreload"," for dependencies — but defaults are tuned for the average app. A dashboard with a 600KB charting library, a storefront with dozens of routes, or a design system shared across micro-frontends each needs different chunking. The cost of getting it wrong shows up directly in Core Web Vitals: a vendor chunk that changes on every deploy forces returning users to re-download it, harming LCP; a route chunk that pulls in a heavy library on the landing page inflates parse time and Total Blocking Time, harming INP.",[15,33,34],{},[35,36,42,43,42,50,42,54,42,57,42,75,42,83,42,89,42,97,42,104,42,109,42,113,42,116,42,120,42,123,42,126,42,132,42,136,42,139,42,142,42,145,42,149,42,152,42,155,42,159,42,162,42,164,42,173,42,177,42,181,42],"svg",{"viewBox":37,"width":38,"role":39,"ariaLabel":40,"style":41},"0 0 760 149","100%","img","Stages of a Vite production build from module graph to emitted, hashed chunks with preload metadata.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[44,45],"rect",{"className":46,"x":48,"y":48,"width":38,"height":38,"fill":49},[47],"svg-canvas","0","#ffffff",[51,52,53],"title",{},"Vite production build pipeline",[55,56,40],"desc",{},[58,59,60],"defs",{},[61,62,69],"marker",{"id":63,"viewBox":64,"refX":65,"refY":66,"markerWidth":67,"markerHeight":67,"orient":68},"face3f29a4","0 0 10 10","9","5","7","auto-start-reverse",[70,71],"path",{"d":72,"fill":73,"style":74},"M0 0 L10 5 L0 10 z","currentColor","fill-opacity:0.7",[44,76],{"x":77,"y":77,"width":78,"height":79,"rx":80,"fill":81,"stroke":73,"style":82},"1","758","147","10","none","stroke-opacity:0.18",[84,85,53],"text",{"x":86,"y":87,"fill":73,"style":88},"28.0","34.0","font-size:16px;font-weight:700",[44,90],{"x":86,"y":91,"width":92,"height":93,"rx":94,"fill":95,"stroke":95,"style":96},"56.0","108.8","71.0","6","#0466c8","fill-opacity:0.14;stroke-opacity:0.9",[84,98,103],{"x":99,"y":100,"fill":73,"style":101,"textAnchor":102},"82.4","78.0","font-size:13px;font-weight:700","middle","Module graph",[84,105,108],{"x":99,"y":106,"fill":73,"style":107,"textAnchor":102},"96.0","font-size:12px","entries + dynamic",[84,110,112],{"x":99,"y":111,"fill":73,"style":107,"textAnchor":102},"112.0","imports",[44,114],{"x":115,"y":91,"width":92,"height":93,"rx":94,"fill":95,"stroke":95,"style":96},"176.8",[84,117,119],{"x":118,"y":100,"fill":73,"style":101,"textAnchor":102},"231.2","Tree-shake",[84,121,122],{"x":118,"y":106,"fill":73,"style":107,"textAnchor":102},"drop unused",[84,124,125],{"x":118,"y":111,"fill":73,"style":107,"textAnchor":102},"exports",[44,127],{"x":128,"y":91,"width":92,"height":93,"rx":94,"fill":129,"stroke":130,"style":131},"325.6","#ffc300","#b8860b","fill-opacity:0.24;stroke-opacity:0.9",[84,133,135],{"x":134,"y":100,"fill":73,"style":101,"textAnchor":102},"380.0","Chunk",[84,137,138],{"x":134,"y":106,"fill":73,"style":107,"textAnchor":102},"automatic +",[84,140,141],{"x":134,"y":111,"fill":73,"style":107,"textAnchor":102},"manualChunks",[44,143],{"x":144,"y":91,"width":92,"height":93,"rx":94,"fill":95,"stroke":95,"style":96},"474.4",[84,146,148],{"x":147,"y":100,"fill":73,"style":101,"textAnchor":102},"528.8","Minify",[84,150,151],{"x":147,"y":106,"fill":73,"style":107,"textAnchor":102},"esbuild \u002F terser",[44,153],{"x":154,"y":91,"width":92,"height":93,"rx":94,"fill":95,"stroke":95,"style":96},"623.2",[84,156,158],{"x":157,"y":100,"fill":73,"style":101,"textAnchor":102},"677.6","Emit",[84,160,161],{"x":157,"y":106,"fill":73,"style":107,"textAnchor":102},"hashes +",[84,163,30],{"x":157,"y":111,"fill":73,"style":107,"textAnchor":102},[165,166],"line",{"x1":167,"y1":168,"x2":169,"y2":168,"stroke":73,"strokeWidth":170,"style":171,"markerEnd":172},"136.8","91.5","174.8","1.5","stroke-opacity:0.6","url(#face3f29a4)",[165,174],{"x1":175,"y1":168,"x2":176,"y2":168,"stroke":73,"strokeWidth":170,"style":171,"markerEnd":172},"285.6","323.6",[165,178],{"x1":179,"y1":168,"x2":180,"y2":168,"stroke":73,"strokeWidth":170,"style":171,"markerEnd":172},"434.4","472.4",[165,182],{"x1":183,"y1":168,"x2":184,"y2":168,"stroke":73,"strokeWidth":170,"style":171,"markerEnd":172},"583.2","621.2",[186,187,189],"h2",{"id":188},"the-metric-degradation-this-topic-addresses","The Metric Degradation This Topic Addresses",[15,191,192],{},"Build configuration problems manifest as three kinds of field regression:",[194,195,196,204,210],"ul",{},[197,198,199,203],"li",{},[200,201,202],"strong",{},"LCP regressions after deploys."," Returning visitors usually have most JavaScript cached. If chunk hashes change whenever any dependency changes — because one large vendor chunk contains everything — every deploy turns returning visitors into first-time visitors for JavaScript. Pages whose LCP depends on client rendering get measurably slower for days after each release.",[197,205,206,209],{},[200,207,208],{},"INP and TBT regressions from over-eager loading."," A chunking strategy that groups rarely used libraries with commonly used ones makes every page parse and evaluate code it does not run. On mid-tier phones, parse and compile cost roughly 1ms per 10–20KB of minified JavaScript before any execution.",[197,211,212,215,216,218],{},[200,213,214],{},"Waterfalls from under-preloaded imports."," A route chunk that imports another chunk that imports a third creates a request chain; without ",[28,217,30],{}," hints for the whole chain, each level costs a round trip.",[15,220,221,222,225,226,229,230,232],{},"The thresholds are the familiar ones: LCP under ",[28,223,224],{},"2.5s",", INP under ",[28,227,228],{},"200ms",", and a lab Total Blocking Time under roughly ",[28,231,228],{}," on a mid-tier mobile profile. The build is not the only factor, but it sets the floor for all three.",[15,234,235],{},[35,236,42,239,42,242,42,245,42,247,42,250,42,252,42,259,42,266,42,271,42,275,42,280,42,284,42,288,42,292,42,296,42,300,42,304,42,308,42,314,42],{"viewBox":237,"width":38,"role":39,"ariaLabel":238,"style":41},"0 0 760 238","Bar chart of JavaScript loaded on the landing route of one application under four chunking strategies.",[44,240],{"className":241,"x":48,"y":48,"width":38,"height":38,"fill":49},[47],[51,243,244],{},"Initial JavaScript by chunking strategy (same app)",[55,246,238],{},[44,248],{"x":77,"y":77,"width":78,"height":249,"rx":80,"fill":81,"stroke":73,"style":82},"236",[84,251,244],{"x":86,"y":87,"fill":73,"style":88},[84,253,258],{"x":254,"y":255,"fill":73,"style":256,"textAnchor":257},"249.2","70.0","font-size:13px","end","Single bundle (no splitting)",[44,260],{"x":261,"y":91,"width":262,"height":263,"rx":264,"fill":129,"stroke":130,"style":265},"261.2","406.8","19","3","fill-opacity:0.7;stroke-opacity:0.9",[84,267,270],{"x":268,"y":255,"fill":73,"style":269},"674.0","font-size:12px;font-weight:600","612KB",[84,272,274],{"x":254,"y":273,"fill":73,"style":256,"textAnchor":257},"101.0","Vite defaults",[44,276],{"x":261,"y":277,"width":278,"height":263,"rx":264,"fill":95,"stroke":95,"style":279},"87.0","164.8","fill-opacity:0.55;stroke-opacity:0.9",[84,281,283],{"x":282,"y":273,"fill":73,"style":269},"432.1","248KB",[84,285,287],{"x":254,"y":286,"fill":73,"style":256,"textAnchor":257},"132.0","+ vendor split by update frequency",[44,289],{"x":261,"y":290,"width":291,"height":263,"rx":264,"fill":95,"stroke":95,"style":279},"118.0","166.8",[84,293,295],{"x":294,"y":286,"fill":73,"style":269},"434.1","251KB",[84,297,299],{"x":254,"y":298,"fill":73,"style":256,"textAnchor":257},"163.0","+ heavy libs lazy per route",[44,301],{"x":261,"y":302,"width":303,"height":263,"rx":264,"fill":95,"stroke":95,"style":279},"149.0","109.0",[84,305,307],{"x":306,"y":298,"fill":73,"style":269},"376.2","164KB",[84,309,313],{"x":86,"y":310,"fill":311,"style":312},"198.0","#51617a","font-size:12.5px","The vendor split barely changes first-load size but sharply improves cache reuse across deploys; lazy libraries cut",[84,315,317],{"x":86,"y":316,"fill":311,"style":312},"216.0","the landing route.",[186,319,321],{"id":320},"prerequisites","Prerequisites",[194,323,324,334,348,357],{},[197,325,326,329,330,333],{},[200,327,328],{},"Vite 5 or later"," (examples use Vite 6\u002F7 option names; ",[28,331,332],{},"build.rollupOptions"," remains the escape hatch for Rollup output settings).",[197,335,336,339,340,343,344,347],{},[200,337,338],{},"A bundle visualiser",": ",[28,341,342],{},"rollup-plugin-visualizer"," (or ",[28,345,346],{},"vite-bundle-visualizer","), configured to emit a treemap with gzip and brotli sizes.",[197,349,350,339,353,356],{},[200,351,352],{},"A production-like preview",[28,354,355],{},"vite build && vite preview"," behind a throttled DevTools profile; never draw conclusions from the dev server, which serves unbundled modules.",[197,358,359,362,363,366],{},[200,360,361],{},"Known browser targets."," Vite's ",[28,364,365],{},"build.target"," defaults to a modern baseline; confirm it matches your analytics before tuning polyfills.",[186,368,370],{"id":369},"_1-environment-setup-make-the-build-observable","1. Environment Setup: Make the Build Observable",[15,372,373],{},"Emit a stats treemap and a manifest on every build so changes in chunking are visible in pull requests.",[375,376,381],"pre",{"className":377,"code":378,"language":379,"meta":380,"style":380},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u002F\u002F vite.config.js\nimport { defineConfig } from 'vite';\nimport { visualizer } from 'rollup-plugin-visualizer';\n\nexport default defineConfig({\n  build: {\n    manifest: true,                       \u002F\u002F maps entries to chunks — useful for SSR preloads and CI checks\n    sourcemap: 'hidden',                  \u002F\u002F source maps for analysis without exposing them via comments\n    reportCompressedSize: true,\n  },\n  plugins: [visualizer({ filename: 'dist\u002Fstats.html', gzipSize: true, brotliSize: true, template: 'treemap' })],\n});\n\u002F\u002F trade-off: hidden source maps are still emitted to dist\u002F. Exclude *.map from\n\u002F\u002F the public deploy (or upload them only to your error tracker) so you do not\n\u002F\u002F publish your original source.\n","javascript","",[28,382,383,391,412,427,434,451,457,473,488,499,507,543,551,557,563],{"__ignoreMap":380},[384,385,387],"span",{"class":165,"line":386},1,[384,388,390],{"class":389},"sjfSM","\u002F\u002F vite.config.js\n",[384,392,394,398,402,405,409],{"class":165,"line":393},2,[384,395,397],{"class":396},"sPARh","import",[384,399,401],{"class":400},"saISM"," { defineConfig } ",[384,403,404],{"class":396},"from",[384,406,408],{"class":407},"sZ8jY"," 'vite'",[384,410,411],{"class":400},";\n",[384,413,415,417,420,422,425],{"class":165,"line":414},3,[384,416,397],{"class":396},[384,418,419],{"class":400}," { visualizer } ",[384,421,404],{"class":396},[384,423,424],{"class":407}," 'rollup-plugin-visualizer'",[384,426,411],{"class":400},[384,428,430],{"class":165,"line":429},4,[384,431,433],{"emptyLinePlaceholder":432},true,"\n",[384,435,437,440,443,447],{"class":165,"line":436},5,[384,438,439],{"class":396},"export",[384,441,442],{"class":396}," default",[384,444,446],{"class":445},"smZ65"," defineConfig",[384,448,450],{"class":449},"sQw3B","({\n",[384,452,454],{"class":165,"line":453},6,[384,455,456],{"class":400},"  build: {\n",[384,458,460,463,467,470],{"class":165,"line":459},7,[384,461,462],{"class":400},"    manifest: ",[384,464,466],{"class":465},"sPXB4","true",[384,468,469],{"class":400},",                       ",[384,471,472],{"class":389},"\u002F\u002F maps entries to chunks — useful for SSR preloads and CI checks\n",[384,474,476,479,482,485],{"class":165,"line":475},8,[384,477,478],{"class":400},"    sourcemap: ",[384,480,481],{"class":407},"'hidden'",[384,483,484],{"class":400},",                  ",[384,486,487],{"class":389},"\u002F\u002F source maps for analysis without exposing them via comments\n",[384,489,491,494,496],{"class":165,"line":490},9,[384,492,493],{"class":400},"    reportCompressedSize: ",[384,495,466],{"class":465},[384,497,498],{"class":400},",\n",[384,500,502,505],{"class":165,"line":501},10,[384,503,504],{"class":400},"  }",[384,506,498],{"class":449},[384,508,510,513,516,519,522,525,527,530,532,535,538,541],{"class":165,"line":509},11,[384,511,512],{"class":400},"  plugins: [",[384,514,515],{"class":445},"visualizer",[384,517,518],{"class":400},"({ filename: ",[384,520,521],{"class":407},"'dist\u002Fstats.html'",[384,523,524],{"class":400},", gzipSize: ",[384,526,466],{"class":465},[384,528,529],{"class":400},", brotliSize: ",[384,531,466],{"class":465},[384,533,534],{"class":400},", template: ",[384,536,537],{"class":407},"'treemap'",[384,539,540],{"class":400}," })]",[384,542,498],{"class":449},[384,544,546,549],{"class":165,"line":545},12,[384,547,548],{"class":449},"})",[384,550,411],{"class":400},[384,552,554],{"class":165,"line":553},13,[384,555,556],{"class":389},"\u002F\u002F trade-off: hidden source maps are still emitted to dist\u002F. Exclude *.map from\n",[384,558,560],{"class":165,"line":559},14,[384,561,562],{"class":389},"\u002F\u002F the public deploy (or upload them only to your error tracker) so you do not\n",[384,564,566],{"class":165,"line":565},15,[384,567,568],{"class":389},"\u002F\u002F publish your original source.\n",[186,570,572],{"id":571},"_2-capture-a-baseline-per-route","2. Capture a Baseline per Route",[15,574,575,576,578,579,582],{},"For each key route, record which chunks load on first navigation, their sizes, and the request chain depth. The manifest makes this scriptable: walk from each entry through ",[28,577,112],{}," and ",[28,580,581],{},"dynamicImports",".",[375,584,586],{"className":377,"code":585,"language":379,"meta":380,"style":380},"\u002F\u002F scripts\u002Froute-chunks.mjs — list the static import closure of each entry.\nimport manifest from '..\u002Fdist\u002F.vite\u002Fmanifest.json' with { type: 'json' };\nfunction closure(key, seen = new Set()) {\n  if (seen.has(key)) return seen;\n  seen.add(key);\n  for (const dep of manifest[key].imports ?? []) closure(dep, seen);\n  return seen;\n}\nfor (const [key, chunk] of Object.entries(manifest)) {\n  if (!chunk.isEntry && !chunk.isDynamicEntry) continue;\n  const files = [...closure(key)].map((k) => manifest[k].file);\n  console.log(key, files.length, 'chunks');\n}\n\u002F\u002F trade-off: the manifest knows static imports but not runtime behaviour. A\n\u002F\u002F dynamic import triggered on load still costs a request; confirm with the\n\u002F\u002F Network panel for routes that matter.\n",[28,587,588,593,617,649,669,680,712,719,724,758,784,822,844,848,853,858],{"__ignoreMap":380},[384,589,590],{"class":165,"line":386},[384,591,592],{"class":389},"\u002F\u002F scripts\u002Froute-chunks.mjs — list the static import closure of each entry.\n",[384,594,595,597,600,602,605,608,611,614],{"class":165,"line":393},[384,596,397],{"class":396},[384,598,599],{"class":400}," manifest ",[384,601,404],{"class":396},[384,603,604],{"class":407}," '..\u002Fdist\u002F.vite\u002Fmanifest.json'",[384,606,607],{"class":396}," with",[384,609,610],{"class":400}," { type: ",[384,612,613],{"class":407},"'json'",[384,615,616],{"class":400}," };\n",[384,618,619,622,625,628,631,634,637,640,643,646],{"class":165,"line":414},[384,620,621],{"class":396},"function",[384,623,624],{"class":445}," closure",[384,626,627],{"class":400},"(",[384,629,630],{"class":449},"key",[384,632,633],{"class":400},", ",[384,635,636],{"class":449},"seen",[384,638,639],{"class":396}," =",[384,641,642],{"class":396}," new",[384,644,645],{"class":445}," Set",[384,647,648],{"class":400},"()) {\n",[384,650,651,654,657,660,663,666],{"class":165,"line":429},[384,652,653],{"class":396},"  if",[384,655,656],{"class":400}," (seen.",[384,658,659],{"class":445},"has",[384,661,662],{"class":400},"(key)) ",[384,664,665],{"class":396},"return",[384,667,668],{"class":400}," seen;\n",[384,670,671,674,677],{"class":165,"line":436},[384,672,673],{"class":400},"  seen.",[384,675,676],{"class":445},"add",[384,678,679],{"class":400},"(key);\n",[384,681,682,685,688,691,694,697,700,703,706,709],{"class":165,"line":453},[384,683,684],{"class":396},"  for",[384,686,687],{"class":400}," (",[384,689,690],{"class":396},"const",[384,692,693],{"class":465}," dep",[384,695,696],{"class":396}," of",[384,698,699],{"class":400}," manifest[key].imports ",[384,701,702],{"class":396},"??",[384,704,705],{"class":400}," []) ",[384,707,708],{"class":445},"closure",[384,710,711],{"class":400},"(dep, seen);\n",[384,713,714,717],{"class":165,"line":459},[384,715,716],{"class":396},"  return",[384,718,668],{"class":400},[384,720,721],{"class":165,"line":475},[384,722,723],{"class":400},"}\n",[384,725,726,729,731,733,736,738,740,743,746,749,752,755],{"class":165,"line":490},[384,727,728],{"class":396},"for",[384,730,687],{"class":400},[384,732,690],{"class":396},[384,734,735],{"class":400}," [",[384,737,630],{"class":465},[384,739,633],{"class":400},[384,741,742],{"class":465},"chunk",[384,744,745],{"class":400},"] ",[384,747,748],{"class":396},"of",[384,750,751],{"class":400}," Object.",[384,753,754],{"class":445},"entries",[384,756,757],{"class":400},"(manifest)) {\n",[384,759,760,762,764,767,770,773,776,779,782],{"class":165,"line":501},[384,761,653],{"class":396},[384,763,687],{"class":400},[384,765,766],{"class":396},"!",[384,768,769],{"class":400},"chunk.isEntry ",[384,771,772],{"class":396},"&&",[384,774,775],{"class":396}," !",[384,777,778],{"class":400},"chunk.isDynamicEntry) ",[384,780,781],{"class":396},"continue",[384,783,411],{"class":400},[384,785,786,789,792,794,796,799,801,804,807,810,813,816,819],{"class":165,"line":509},[384,787,788],{"class":396},"  const",[384,790,791],{"class":465}," files",[384,793,639],{"class":396},[384,795,735],{"class":400},[384,797,798],{"class":396},"...",[384,800,708],{"class":445},[384,802,803],{"class":400},"(key)].",[384,805,806],{"class":445},"map",[384,808,809],{"class":400},"((",[384,811,812],{"class":449},"k",[384,814,815],{"class":400},") ",[384,817,818],{"class":396},"=>",[384,820,821],{"class":400}," manifest[k].file);\n",[384,823,824,827,830,833,836,838,841],{"class":165,"line":545},[384,825,826],{"class":400},"  console.",[384,828,829],{"class":445},"log",[384,831,832],{"class":400},"(key, files.",[384,834,835],{"class":465},"length",[384,837,633],{"class":400},[384,839,840],{"class":407},"'chunks'",[384,842,843],{"class":400},");\n",[384,845,846],{"class":165,"line":553},[384,847,723],{"class":400},[384,849,850],{"class":165,"line":559},[384,851,852],{"class":389},"\u002F\u002F trade-off: the manifest knows static imports but not runtime behaviour. A\n",[384,854,855],{"class":165,"line":565},[384,856,857],{"class":389},"\u002F\u002F dynamic import triggered on load still costs a request; confirm with the\n",[384,859,861],{"class":165,"line":860},16,[384,862,863],{"class":389},"\u002F\u002F Network panel for routes that matter.\n",[186,865,867],{"id":866},"_3-isolate-the-bottleneck","3. Isolate the Bottleneck",[15,869,870],{},"Open the treemap and look for three patterns: a large library inside the entry or a shared chunk that only one route uses; the same library appearing in several chunks (duplication); and a vendor chunk that contains both stable libraries and frequently updated internal packages. Each points to a different fix in the guides below.",[15,872,873],{},[35,874,42,877,42,880,42,883,42,885,42,888,42,890,42,895,42,902,42,906,42,910,42,913,42,917,42,921,42,925,42,927,42,930,42,932,42,935,42,938,42,942,42,944,42,947,42,949,42,952,42,956,42,960,42,963,42,965,42,969,42,971,42,974,42,977,42,981,42,983,42,986,42,988,42],{"viewBox":875,"width":38,"role":39,"ariaLabel":876,"style":41},"0 0 760 244","Mapping of patterns visible in a Vite bundle treemap to the configuration change that addresses each.",[44,878],{"className":879,"x":48,"y":48,"width":38,"height":38,"fill":49},[47],[51,881,882],{},"Treemap pattern to fix",[55,884,876],{},[44,886],{"x":77,"y":77,"width":78,"height":887,"rx":80,"fill":81,"stroke":73,"style":82},"242",[84,889,882],{"x":86,"y":87,"fill":73,"style":88},[44,891],{"x":86,"y":91,"width":892,"height":893,"rx":48,"fill":73,"stroke":73,"style":894},"210.0","30.0","fill-opacity:0.06;stroke-opacity:0.4",[84,896,901],{"x":897,"y":898,"fill":73,"style":899,"textAnchor":900},"38.0","75.5","font-size:12.5px;font-weight:700","start","Pattern in treemap",[44,903],{"x":904,"y":91,"width":905,"height":893,"rx":48,"fill":73,"stroke":73,"style":894},"238.0","247.0",[84,907,909],{"x":908,"y":898,"fill":73,"style":899,"textAnchor":102},"361.5","Cost",[44,911],{"x":912,"y":91,"width":905,"height":893,"rx":48,"fill":73,"stroke":73,"style":894},"485.0",[84,914,916],{"x":915,"y":898,"fill":73,"style":899,"textAnchor":102},"608.5","Fix",[44,918],{"x":86,"y":919,"width":892,"height":893,"rx":48,"fill":81,"stroke":73,"style":920},"86.0","stroke-opacity:0.35",[84,922,924],{"x":897,"y":923,"fill":73,"style":899,"textAnchor":900},"105.5","Heavy lib in entry chunk",[44,926],{"x":904,"y":919,"width":905,"height":893,"rx":48,"fill":129,"stroke":130,"style":131},[84,928,929],{"x":908,"y":923,"fill":73,"style":107,"textAnchor":102},"parse on every route",[44,931],{"x":912,"y":919,"width":905,"height":893,"rx":48,"fill":81,"stroke":73,"style":920},[84,933,934],{"x":915,"y":923,"fill":73,"style":107,"textAnchor":102},"Dynamic import at the usage site",[44,936],{"x":86,"y":937,"width":892,"height":893,"rx":48,"fill":81,"stroke":73,"style":920},"116.0",[84,939,941],{"x":897,"y":940,"fill":73,"style":899,"textAnchor":900},"135.5","Same lib in several chunks",[44,943],{"x":904,"y":937,"width":905,"height":893,"rx":48,"fill":129,"stroke":130,"style":131},[84,945,946],{"x":908,"y":940,"fill":73,"style":107,"textAnchor":102},"duplicate bytes",[44,948],{"x":912,"y":937,"width":905,"height":893,"rx":48,"fill":81,"stroke":73,"style":920},[84,950,951],{"x":915,"y":940,"fill":73,"style":107,"textAnchor":102},"manualChunks group or dedupe",[44,953],{"x":86,"y":954,"width":892,"height":955,"rx":48,"fill":81,"stroke":73,"style":920},"146.0","46.0",[84,957,959],{"x":897,"y":958,"fill":73,"style":899,"textAnchor":900},"165.5","Vendor mixes stable and volatile",[84,961,28],{"x":897,"y":962,"fill":73,"style":899,"textAnchor":900},"181.5",[44,964],{"x":904,"y":954,"width":905,"height":955,"rx":48,"fill":129,"stroke":130,"style":131},[84,966,968],{"x":908,"y":967,"fill":73,"style":107,"textAnchor":102},"173.5","cache busted every deploy",[44,970],{"x":912,"y":954,"width":905,"height":955,"rx":48,"fill":81,"stroke":73,"style":920},[84,972,973],{"x":915,"y":967,"fill":73,"style":107,"textAnchor":102},"Split vendor by update frequency",[44,975],{"x":86,"y":976,"width":892,"height":893,"rx":48,"fill":81,"stroke":73,"style":920},"192.0",[84,978,980],{"x":897,"y":979,"fill":73,"style":899,"textAnchor":900},"211.5","Deep import chains",[44,982],{"x":904,"y":976,"width":905,"height":893,"rx":48,"fill":73,"stroke":73,"style":894},[84,984,985],{"x":908,"y":979,"fill":73,"style":107,"textAnchor":102},"round-trip waterfall",[44,987],{"x":912,"y":976,"width":905,"height":893,"rx":48,"fill":81,"stroke":73,"style":920},[84,989,990],{"x":915,"y":979,"fill":73,"style":107,"textAnchor":102},"Check modulepreload coverage",[186,992,994],{"id":993},"_4-apply-the-fix","4. Apply the Fix",[15,996,997],{},"The highest-leverage changes, in typical order of impact:",[999,1000,1001,1007,1021,1031],"ol",{},[197,1002,1003,1006],{},[200,1004,1005],{},"Move heavy, route-specific libraries behind dynamic imports"," at the point of use — a chart library imported only when the chart mounts.",[197,1008,1009,1015,1016,1020],{},[200,1010,1011,1012,1014],{},"Define ",[28,1013,141],{}," that group by update frequency",", so framework and stable vendor code live in long-cached chunks separate from your frequently changing application code. ",[19,1017,1019],{"href":1018},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fvite-and-rollup-build-optimization\u002Fconfiguring-manualchunks-in-vite\u002F","Configuring manualChunks in Vite"," shows a strategy that avoids over-splitting.",[197,1022,1023,1026,1027,582],{},[200,1024,1025],{},"Verify modulepreload coverage"," for dynamic routes so chains load in parallel; see ",[19,1028,1030],{"href":1029},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fvite-and-rollup-build-optimization\u002Fcontrolling-vite-modulepreload-for-dynamic-imports\u002F","controlling Vite modulepreload for dynamic imports",[197,1032,1033,1038,1039,582],{},[200,1034,1035,1036],{},"Set ",[28,1037,365],{}," to the browsers you actually support, avoiding unnecessary syntax down-levelling — covered in ",[19,1040,1042],{"href":1041},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fpolyfills-and-differential-serving\u002Ftargeting-baseline-browsers-with-esbuild-and-swc\u002F","targeting baseline browsers with esbuild and SWC",[186,1044,1046],{"id":1045},"deconstructing-javascript-cost-on-the-critical-path","Deconstructing JavaScript Cost on the Critical Path",[15,1048,1049],{},"From the browser's perspective, each chunk on the critical path costs four phases, each with its own lever in the build:",[1051,1052,1053,1072],"table",{},[1054,1055,1056],"thead",{},[1057,1058,1059,1063,1066,1069],"tr",{},[1060,1061,1062],"th",{},"Phase",[1060,1064,1065],{},"What drives it",[1060,1067,1068],{},"Build lever",[1060,1070,1071],{},"Rough budget (mid-tier mobile)",[1073,1074,1075,1090,1104,1118],"tbody",{},[1057,1076,1077,1081,1084,1087],{},[1078,1079,1080],"td",{},"Request + download",[1078,1082,1083],{},"bytes, chain depth, cache hit",[1078,1085,1086],{},"chunking, preload, hashing",[1078,1088,1089],{},"\u003C 300ms for initial JS",[1057,1091,1092,1095,1098,1101],{},[1078,1093,1094],{},"Parse + compile",[1078,1096,1097],{},"uncompressed size, syntax",[1078,1099,1100],{},"target, minifier, splitting",[1078,1102,1103],{},"\u003C 100ms",[1057,1105,1106,1109,1112,1115],{},[1078,1107,1108],{},"Evaluate (module init)",[1078,1110,1111],{},"top-level side effects",[1078,1113,1114],{},"sideEffects, lazy init",[1078,1116,1117],{},"\u003C 50ms per chunk",[1057,1119,1120,1123,1126,1129],{},[1078,1121,1122],{},"Execute (render\u002Fhydrate)",[1078,1124,1125],{},"application work",[1078,1127,1128],{},"route splitting, islands",[1078,1130,1131],{},"\u003C 200ms total",[15,1133,1134],{},[35,1135,42,1138,42,1141,42,1144,42,1146,42,1149,42,1151,42,1155,42,1159,42,1165,42,1168,42,1173,42,1179,42,1183,42,1187,42,1191,42,1195,42,1201,42,1206,42,1210,42,1213,42,1217,42,1220,42,1224,42,1228,42,1232,42,1236,42],{"viewBox":1136,"width":38,"role":39,"ariaLabel":1137,"style":41},"0 0 760 178","Timeline of downloading, parsing, evaluating and executing the initial JavaScript of a Vite app, with a 50 millisecond long-task marker.",[44,1139],{"className":1140,"x":48,"y":48,"width":38,"height":38,"fill":49},[47],[51,1142,1143],{},"Critical-path cost of an initial Vite bundle on a mid-tier phone",[55,1145,1137],{},[44,1147],{"x":77,"y":77,"width":78,"height":1148,"rx":80,"fill":81,"stroke":73,"style":82},"176",[84,1150,1143],{"x":86,"y":87,"fill":73,"style":88},[84,1152,1154],{"x":86,"y":1153,"fill":73,"style":899},"74.0","Network",[44,1156],{"x":111,"y":91,"width":1157,"height":1158,"rx":264,"fill":95,"stroke":95,"style":96},"230.8","26.0",[84,1160,1164],{"x":1161,"y":1162,"fill":73,"style":1163,"textAnchor":102},"227.3","73.5","font-size:11.5px","download 248KB",[84,1166,1167],{"x":86,"y":111,"fill":73,"style":899},"Main thread",[44,1169],{"x":1170,"y":1171,"width":1172,"height":1158,"rx":264,"fill":129,"stroke":130,"style":131},"344.2","94.0","80.5",[84,1174,1178],{"x":1175,"y":1176,"fill":73,"style":1177,"textAnchor":102},"384.4","111.5","font-size:10.5px","parse + compile",[44,1180],{"x":1181,"y":1171,"width":1182,"height":1158,"rx":264,"fill":73,"stroke":73,"style":894},"426.2","46.3",[84,1184,1186],{"x":1185,"y":1176,"fill":73,"style":1163,"textAnchor":102},"449.3","eval",[44,1188],{"x":1189,"y":1171,"width":1190,"height":1158,"rx":264,"fill":129,"stroke":130,"style":131},"474.0","196.6",[84,1192,1194],{"x":1193,"y":1176,"fill":73,"style":1163,"textAnchor":102},"572.3","render + hydrate",[165,1196],{"x1":1197,"y1":1198,"x2":1199,"y2":1198,"stroke":73,"style":1200},"111.2","134.0","726.0","stroke-opacity:0.4",[84,1202,1205],{"x":1203,"y":1204,"fill":311,"style":1163,"textAnchor":102},"121.9","151.0","0ms",[84,1207,1209],{"x":1208,"y":1204,"fill":311,"style":1163,"textAnchor":102},"179.5","100ms",[84,1211,228],{"x":1212,"y":1204,"fill":311,"style":1163,"textAnchor":102},"247.8",[84,1214,1216],{"x":1215,"y":1204,"fill":311,"style":1163,"textAnchor":102},"316.1","300ms",[84,1218,1219],{"x":1175,"y":1204,"fill":311,"style":1163,"textAnchor":102},"400ms",[84,1221,1223],{"x":1222,"y":1204,"fill":311,"style":1163,"textAnchor":102},"452.8","500ms",[84,1225,1227],{"x":1226,"y":1204,"fill":311,"style":1163,"textAnchor":102},"521.1","600ms",[84,1229,1231],{"x":1230,"y":1204,"fill":311,"style":1163,"textAnchor":102},"589.4","700ms",[84,1233,1235],{"x":1234,"y":1204,"fill":311,"style":1163,"textAnchor":102},"657.7","800ms",[84,1237,1239],{"x":1238,"y":1204,"fill":311,"style":1163,"textAnchor":102},"715.3","900ms",[15,1241,1242,1243,1247,1248,582],{},"Compressed transfer size governs the first phase only. The others scale with ",[1244,1245,1246],"em",{},"uncompressed"," code size and with what that code does when evaluated, which is why a budget expressed purely in gzip kilobytes under-predicts INP problems — the argument made in ",[19,1249,1251],{"href":1250},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fjavascript-performance-budgets\u002Fwhy-compressed-size-is-the-wrong-budget\u002F","why compressed size is the wrong budget",[186,1253,1255],{"id":1254},"advanced-diagnostics-and-edge-cases","Advanced Diagnostics and Edge Cases",[15,1257,1258,1261,1262,1265],{},[200,1259,1260],{},"CSS code splitting and FOUC."," Vite splits CSS per async chunk and injects it when the chunk loads. If a route chunk's CSS arrives after its JavaScript renders, users see a flash of unstyled content and layout shift. Vite inserts the CSS before resolving the dynamic import, but custom loaders that bypass ",[28,1263,1264],{},"import()"," can break that ordering.",[15,1267,1268,42,1271,1273],{},[200,1269,1270],{},"Circular chunks.",[28,1272,141],{}," functions that assign modules by path can create chunks that import each other, which Rollup resolves but which can cause evaluation-order bugs and extra requests. The visualiser's \"network\" view helps spot cycles.",[15,1275,1276,1279,1280,1283],{},[200,1277,1278],{},"Shared dependencies between SSR and client."," In SSR setups, the server bundle and client bundle are built separately; a module marked ",[28,1281,1282],{},"noExternal"," for SSR may end up duplicated or differently split. Check both manifests.",[15,1285,1286,1289,1290,1293,1294,1296],{},[200,1287,1288],{},"Rolldown differences."," As Vite moves to the Rust-based Rolldown bundler, chunking heuristics and ",[28,1291,1292],{},"advancedChunks"," options differ from Rollup's ",[28,1295,141],{},". Re-run your baseline after upgrading and compare chunk lists rather than assuming identical output.",[15,1298,1299,42,1302,1305],{},[200,1300,1301],{},"Legacy plugin output.",[28,1303,1304],{},"@vitejs\u002Fplugin-legacy"," emits a second set of chunks with polyfills for older browsers. It is served only to browsers that need it, but doubles build time and can confuse size reports that sum all output.",[186,1307,1309],{"id":1308},"validation-and-budgeting","Validation and Budgeting",[15,1311,1312,1313,1317],{},"Lock in chunk sizes per entry in CI. A small script over the manifest can fail the build when any route's initial closure grows beyond its budget, and ",[19,1314,1316],{"href":1315},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fjavascript-performance-budgets\u002Fenforcing-bundle-budgets-with-size-limit\u002F","enforcing bundle budgets with size-limit"," provides a ready-made tool.",[375,1319,1321],{"className":377,"code":1320,"language":379,"meta":380,"style":380},"\u002F\u002F scripts\u002Fcheck-route-budgets.mjs\nimport { statSync } from 'node:fs';\nimport manifest from '..\u002Fdist\u002F.vite\u002Fmanifest.json' with { type: 'json' };\nconst budgets = { 'src\u002Fmain.ts': 180_000, 'src\u002Froutes\u002FCheckout.vue': 90_000 };   \u002F\u002F bytes, uncompressed\nfor (const [entry, limit] of Object.entries(budgets)) {\n  const seen = new Set(); const walk = (k) => { if (seen.has(k)) return; seen.add(k); (manifest[k].imports ?? []).forEach(walk); };\n  walk(entry);\n  const bytes = [...seen].reduce((n, k) => n + statSync(`dist\u002F${manifest[k].file}`).size, 0);\n  if (bytes > limit) { console.error(`${entry}: ${bytes} > ${limit}`); process.exitCode = 1; }\n}\n\u002F\u002F trade-off: uncompressed byte budgets track parse cost well but ignore what\n\u002F\u002F the code does at runtime. Pair them with a Lighthouse TBT assertion on the\n\u002F\u002F same routes.\n",[28,1322,1323,1328,1342,1360,1396,1425,1489,1497,1567,1615,1619,1624,1629],{"__ignoreMap":380},[384,1324,1325],{"class":165,"line":386},[384,1326,1327],{"class":389},"\u002F\u002F scripts\u002Fcheck-route-budgets.mjs\n",[384,1329,1330,1332,1335,1337,1340],{"class":165,"line":393},[384,1331,397],{"class":396},[384,1333,1334],{"class":400}," { statSync } ",[384,1336,404],{"class":396},[384,1338,1339],{"class":407}," 'node:fs'",[384,1341,411],{"class":400},[384,1343,1344,1346,1348,1350,1352,1354,1356,1358],{"class":165,"line":414},[384,1345,397],{"class":396},[384,1347,599],{"class":400},[384,1349,404],{"class":396},[384,1351,604],{"class":407},[384,1353,607],{"class":396},[384,1355,610],{"class":400},[384,1357,613],{"class":407},[384,1359,616],{"class":400},[384,1361,1362,1364,1367,1369,1372,1375,1377,1380,1382,1385,1387,1390,1393],{"class":165,"line":429},[384,1363,690],{"class":396},[384,1365,1366],{"class":465}," budgets",[384,1368,639],{"class":396},[384,1370,1371],{"class":400}," { ",[384,1373,1374],{"class":407},"'src\u002Fmain.ts'",[384,1376,339],{"class":400},[384,1378,1379],{"class":465},"180_000",[384,1381,633],{"class":400},[384,1383,1384],{"class":407},"'src\u002Froutes\u002FCheckout.vue'",[384,1386,339],{"class":400},[384,1388,1389],{"class":465},"90_000",[384,1391,1392],{"class":400}," };   ",[384,1394,1395],{"class":389},"\u002F\u002F bytes, uncompressed\n",[384,1397,1398,1400,1402,1404,1406,1409,1411,1414,1416,1418,1420,1422],{"class":165,"line":436},[384,1399,728],{"class":396},[384,1401,687],{"class":400},[384,1403,690],{"class":396},[384,1405,735],{"class":400},[384,1407,1408],{"class":465},"entry",[384,1410,633],{"class":400},[384,1412,1413],{"class":465},"limit",[384,1415,745],{"class":400},[384,1417,748],{"class":396},[384,1419,751],{"class":400},[384,1421,754],{"class":445},[384,1423,1424],{"class":400},"(budgets)) {\n",[384,1426,1427,1429,1432,1434,1436,1438,1441,1443,1446,1448,1450,1452,1454,1456,1458,1461,1463,1465,1468,1470,1473,1475,1478,1480,1483,1486],{"class":165,"line":453},[384,1428,788],{"class":396},[384,1430,1431],{"class":465}," seen",[384,1433,639],{"class":396},[384,1435,642],{"class":396},[384,1437,645],{"class":445},[384,1439,1440],{"class":400},"(); ",[384,1442,690],{"class":396},[384,1444,1445],{"class":445}," walk",[384,1447,639],{"class":396},[384,1449,687],{"class":400},[384,1451,812],{"class":449},[384,1453,815],{"class":400},[384,1455,818],{"class":396},[384,1457,1371],{"class":400},[384,1459,1460],{"class":396},"if",[384,1462,656],{"class":400},[384,1464,659],{"class":445},[384,1466,1467],{"class":400},"(k)) ",[384,1469,665],{"class":396},[384,1471,1472],{"class":400},"; seen.",[384,1474,676],{"class":445},[384,1476,1477],{"class":400},"(k); (manifest[k].imports ",[384,1479,702],{"class":396},[384,1481,1482],{"class":400}," []).",[384,1484,1485],{"class":445},"forEach",[384,1487,1488],{"class":400},"(walk); };\n",[384,1490,1491,1494],{"class":165,"line":459},[384,1492,1493],{"class":445},"  walk",[384,1495,1496],{"class":400},"(entry);\n",[384,1498,1499,1501,1504,1506,1508,1510,1513,1516,1518,1521,1523,1525,1527,1529,1532,1535,1538,1540,1543,1546,1549,1551,1554,1557,1560,1563,1565],{"class":165,"line":475},[384,1500,788],{"class":396},[384,1502,1503],{"class":465}," bytes",[384,1505,639],{"class":396},[384,1507,735],{"class":400},[384,1509,798],{"class":396},[384,1511,1512],{"class":400},"seen].",[384,1514,1515],{"class":445},"reduce",[384,1517,809],{"class":400},[384,1519,1520],{"class":449},"n",[384,1522,633],{"class":400},[384,1524,812],{"class":449},[384,1526,815],{"class":400},[384,1528,818],{"class":396},[384,1530,1531],{"class":400}," n ",[384,1533,1534],{"class":396},"+",[384,1536,1537],{"class":445}," statSync",[384,1539,627],{"class":400},[384,1541,1542],{"class":407},"`dist\u002F${",[384,1544,1545],{"class":400},"manifest",[384,1547,1548],{"class":407},"[",[384,1550,812],{"class":400},[384,1552,1553],{"class":407},"].",[384,1555,1556],{"class":400},"file",[384,1558,1559],{"class":407},"}`",[384,1561,1562],{"class":400},").size, ",[384,1564,48],{"class":465},[384,1566,843],{"class":400},[384,1568,1569,1571,1574,1577,1580,1583,1585,1588,1590,1593,1596,1599,1601,1603,1606,1609,1612],{"class":165,"line":490},[384,1570,653],{"class":396},[384,1572,1573],{"class":400}," (bytes ",[384,1575,1576],{"class":396},">",[384,1578,1579],{"class":400}," limit) { console.",[384,1581,1582],{"class":445},"error",[384,1584,627],{"class":400},[384,1586,1587],{"class":407},"`${",[384,1589,1408],{"class":400},[384,1591,1592],{"class":407},"}: ${",[384,1594,1595],{"class":400},"bytes",[384,1597,1598],{"class":407},"} > ${",[384,1600,1413],{"class":400},[384,1602,1559],{"class":407},[384,1604,1605],{"class":400},"); process.exitCode ",[384,1607,1608],{"class":396},"=",[384,1610,1611],{"class":465}," 1",[384,1613,1614],{"class":400},"; }\n",[384,1616,1617],{"class":165,"line":501},[384,1618,723],{"class":400},[384,1620,1621],{"class":165,"line":509},[384,1622,1623],{"class":389},"\u002F\u002F trade-off: uncompressed byte budgets track parse cost well but ignore what\n",[384,1625,1626],{"class":165,"line":545},[384,1627,1628],{"class":389},"\u002F\u002F the code does at runtime. Pair them with a Lighthouse TBT assertion on the\n",[384,1630,1631],{"class":165,"line":553},[384,1632,1633],{"class":389},"\u002F\u002F same routes.\n",[15,1635,1636],{},"Track the hash stability of vendor chunks across releases as well: a script that compares chunk filenames between the previous and current deploy shows what share of JavaScript returning users must re-download.",[15,1638,1639],{},[35,1640,42,1643,42,1646,42,1649,42,1651,42,1654,42,1656,42,1660,42,1664,42,1667,42,1669,42,1672,42,1676,42,1679,42,1682,42],{"viewBox":1641,"width":38,"role":39,"ariaLabel":1642,"style":41},"0 0 760 163","Bar chart of the share of JavaScript bytes whose hashes change after a routine deploy, under three chunking strategies.",[44,1644],{"className":1645,"x":48,"y":48,"width":38,"height":38,"fill":49},[47],[51,1647,1648],{},"JavaScript re-downloaded by returning users after a typical deploy",[55,1650,1642],{},[44,1652],{"x":77,"y":77,"width":78,"height":1653,"rx":80,"fill":81,"stroke":73,"style":82},"161",[84,1655,1648],{"x":86,"y":87,"fill":73,"style":88},[84,1657,1659],{"x":1658,"y":255,"fill":73,"style":256,"textAnchor":257},"262.9","One vendor chunk with internal packages",[44,1661],{"x":1662,"y":91,"width":1663,"height":263,"rx":264,"fill":129,"stroke":130,"style":265},"274.9","393.1",[84,1665,1666],{"x":268,"y":255,"fill":73,"style":269},"78%",[84,1668,274],{"x":1658,"y":273,"fill":73,"style":256,"textAnchor":257},[44,1670],{"x":1662,"y":277,"width":1671,"height":263,"rx":264,"fill":95,"stroke":95,"style":279},"206.6",[84,1673,1675],{"x":1674,"y":273,"fill":73,"style":269},"487.5","41%",[84,1677,1678],{"x":1658,"y":286,"fill":73,"style":256,"textAnchor":257},"Vendor split by update frequency",[44,1680],{"x":1662,"y":290,"width":1681,"height":263,"rx":264,"fill":95,"stroke":95,"style":279},"60.5",[84,1683,1685],{"x":1684,"y":286,"fill":73,"style":269},"341.4","12%",[186,1687,1689],{"id":1688},"build-targets-minification-and-syntax-cost","Build Targets, Minification and Syntax Cost",[15,1691,1692,1694,1695,1698],{},[28,1693,365],{}," controls which JavaScript syntax esbuild down-levels. Vite's default modern target leaves classes, optional chaining, async\u002Fawait and other ES2020+ syntax intact, which keeps output small and fast to parse. Setting a lower target (",[28,1696,1697],{},"es2015",", or matching a broad Browserslist query) forces transformations that inflate code — async functions become generator-based state machines, classes become functions with helper calls — typically adding 10–20% to bundle size and measurable parse time. Choose the target from real analytics: if 99.5% of your traffic runs browsers from the last three years, target them and serve a legacy build (or nothing) to the rest.",[15,1700,1701,1702,1705,1706,1709,1710,1713,1714,1717],{},"Minification is the other lever. esbuild minifies quickly and well; enabling ",[28,1703,1704],{},"build.minify: 'terser'"," with multiple passes can save a few percent more at a large build-time cost. More impactful are the settings that let the minifier remove code: ",[28,1707,1708],{},"define"," replacements for feature flags and environment checks (so ",[28,1711,1712],{},"if (import.meta.env.DEV)"," branches disappear entirely), and ",[28,1715,1716],{},"esbuild.drop: ['console', 'debugger']"," for production builds, which removes logging calls whose arguments may themselves be expensive to evaluate.",[375,1719,1721],{"className":377,"code":1720,"language":379,"meta":380,"style":380},"\u002F\u002F vite.config.js — syntax and dead-code settings for production.\nexport default defineConfig(({ mode }) => ({\n  build: { target: ['chrome111', 'edge111', 'firefox114', 'safari16.4'], cssMinify: 'lightningcss' },\n  esbuild: mode === 'production' ? { drop: ['console', 'debugger'], legalComments: 'none' } : {},\n  define: { __ENABLE_DEVTOOLS_PANEL__: JSON.stringify(mode !== 'production') },\n}));\n\u002F\u002F trade-off: dropping console removes ALL console calls, including console.error\n\u002F\u002F you may rely on for production diagnostics. Use pure: ['console.log'] instead\n\u002F\u002F to remove only the calls you consider noise.\n",[28,1722,1723,1728,1744,1778,1820,1846,1853,1858,1863],{"__ignoreMap":380},[384,1724,1725],{"class":165,"line":386},[384,1726,1727],{"class":389},"\u002F\u002F vite.config.js — syntax and dead-code settings for production.\n",[384,1729,1730,1732,1734,1736,1739,1741],{"class":165,"line":393},[384,1731,439],{"class":396},[384,1733,442],{"class":396},[384,1735,446],{"class":445},[384,1737,1738],{"class":449},"(({ mode }) ",[384,1740,818],{"class":396},[384,1742,1743],{"class":449}," ({\n",[384,1745,1746,1749,1752,1754,1757,1759,1762,1764,1767,1770,1773,1776],{"class":165,"line":414},[384,1747,1748],{"class":400},"  build: { target: [",[384,1750,1751],{"class":407},"'chrome111'",[384,1753,633],{"class":400},[384,1755,1756],{"class":407},"'edge111'",[384,1758,633],{"class":400},[384,1760,1761],{"class":407},"'firefox114'",[384,1763,633],{"class":400},[384,1765,1766],{"class":407},"'safari16.4'",[384,1768,1769],{"class":400},"], cssMinify: ",[384,1771,1772],{"class":407},"'lightningcss'",[384,1774,1775],{"class":400}," }",[384,1777,498],{"class":449},[384,1779,1780,1783,1786,1789,1792,1795,1798,1800,1803,1806,1809,1812,1815,1818],{"class":165,"line":429},[384,1781,1782],{"class":400},"  esbuild: mode ",[384,1784,1785],{"class":396},"===",[384,1787,1788],{"class":407}," 'production'",[384,1790,1791],{"class":396}," ?",[384,1793,1794],{"class":400}," { drop: [",[384,1796,1797],{"class":407},"'console'",[384,1799,633],{"class":400},[384,1801,1802],{"class":407},"'debugger'",[384,1804,1805],{"class":400},"], legalComments: ",[384,1807,1808],{"class":407},"'none'",[384,1810,1811],{"class":400}," } ",[384,1813,1814],{"class":396},":",[384,1816,1817],{"class":400}," {}",[384,1819,498],{"class":449},[384,1821,1822,1825,1828,1830,1833,1836,1839,1841,1844],{"class":165,"line":436},[384,1823,1824],{"class":400},"  define: { __ENABLE_DEVTOOLS_PANEL__: ",[384,1826,1827],{"class":465},"JSON",[384,1829,582],{"class":400},[384,1831,1832],{"class":445},"stringify",[384,1834,1835],{"class":400},"(mode ",[384,1837,1838],{"class":396},"!==",[384,1840,1788],{"class":407},[384,1842,1843],{"class":400},") }",[384,1845,498],{"class":449},[384,1847,1848,1851],{"class":165,"line":453},[384,1849,1850],{"class":449},"}))",[384,1852,411],{"class":400},[384,1854,1855],{"class":165,"line":459},[384,1856,1857],{"class":389},"\u002F\u002F trade-off: dropping console removes ALL console calls, including console.error\n",[384,1859,1860],{"class":165,"line":475},[384,1861,1862],{"class":389},"\u002F\u002F you may rely on for production diagnostics. Use pure: ['console.log'] instead\n",[384,1864,1865],{"class":165,"line":490},[384,1866,1867],{"class":389},"\u002F\u002F to remove only the calls you consider noise.\n",[186,1869,1871],{"id":1870},"ssr-manifests-and-server-emitted-hints","SSR Manifests and Server-Emitted Hints",[15,1873,1874,1875,1878,1879,1882,1883,582],{},"In server-rendered setups — Nuxt, SvelteKit, Vike, or a custom Express + Vite integration — the client build's manifest is the bridge between what the server renders and what the browser should fetch. The server knows the route; the manifest knows the route's chunks and CSS. Emitting ",[28,1876,1877],{},"\u003Clink rel=\"modulepreload\">"," for those chunks and ",[28,1880,1881],{},"\u003Clink rel=\"stylesheet\">"," for route CSS in the HTML head lets the browser start them during HTML parsing instead of after the entry executes. Frameworks do this automatically; custom integrations often forget, producing a waterfall where the route chunk is discovered only after hydration starts. If your server streams HTML, emit these hints in the first flush so they are not delayed by slow data — the subject of ",[19,1884,1886],{"href":1885},"\u002Fnetwork-protocol-optimization\u002Fstreaming-ssr-and-early-flush\u002Fflushing-the-document-head-early\u002F","flushing the document head early",[186,1888,1890],{"id":1889},"a-release-checklist-for-build-changes","A Release Checklist for Build Changes",[15,1892,1893,1894,1896],{},"Build configuration changes are deceptively risky: a one-line ",[28,1895,141],{}," edit can reshuffle every hash and change evaluation order. Treat them like code changes with their own checks:",[999,1898,1899,1902,1905,1908,1911],{},[197,1900,1901],{},"Compare the treemap before and after, per route, and record initial-JS deltas in the pull request.",[197,1903,1904],{},"Confirm no package now appears in more than one chunk.",[197,1906,1907],{},"Run the application's end-to-end tests against the production build, not the dev server, to catch evaluation-order bugs.",[197,1909,1910],{},"Measure LCP and TBT for two key routes in a throttled lab run.",[197,1912,1913,1914,582],{},"After deploy, watch returning-visitor LCP and error rates for chunk load failures for a few days — new chunk names mean old tabs may request files that no longer exist, covered in ",[19,1915,1917],{"href":1916},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002Fhandling-chunk-load-errors-after-a-deploy\u002F","handling chunk load errors after a deploy",[186,1919,1921],{"id":1920},"faq","FAQ",[1923,1924,1927,1931],"details",{"className":1925},[1926],"faq-item",[1928,1929,1930],"summary",{},"Should I disable Vite's automatic chunk splitting and do everything manually?",[15,1932,1933,1934,1936],{},"Rarely. The automatic strategy handles dynamic-import boundaries and shared modules well. ",[28,1935,141],{}," is best used narrowly — to group stable vendor code for caching and to keep a handful of heavy libraries together — while leaving route splitting to the defaults. Fully manual chunking tends to rot as the codebase changes.",[1923,1938,1940,1943],{"className":1939},[1926],[1928,1941,1942],{},"Is esbuild or terser the better minifier for Vite builds?",[15,1944,1945,1946,582],{},"esbuild (the default) is dramatically faster and produces output within a few percent of terser's size for most code. Terser can squeeze slightly more, which matters only for very large bundles. The comparison is covered in ",[19,1947,1949],{"href":1948},"\u002Fjavascript-bundle-optimization-code-splitting\u002Ftree-shaking-and-dead-code-elimination\u002Fesbuild-vs-terser-for-production-minification\u002F","esbuild vs Terser for production minification",[1923,1951,1953,1956],{"className":1952},[1926],[1928,1954,1955],{},"Do these settings apply to Nuxt, SvelteKit and Astro?",[15,1957,1958,1959,1962,1963,1965,1966,1969],{},"Yes — all three build on Vite, and expose ",[28,1960,1961],{},"vite"," configuration (Nuxt via ",[28,1964,1961],{}," in ",[28,1967,1968],{},"nuxt.config",", SvelteKit and Astro via their config files). Framework defaults sometimes override chunking, so inspect the output treemap rather than assuming your Vite options were applied.",[1923,1971,1973,1976],{"className":1972},[1926],[1928,1974,1975],{},"How often should chunking be revisited?",[15,1977,1978],{},"Whenever the dependency list changes materially and at least quarterly. Chunking that was right for twenty dependencies drifts as libraries are added, upgraded or replaced. A scheduled review of the treemap and the per-route budgets keeps the configuration aligned with the code it describes, and is far cheaper than discovering in field data that the landing route has quietly grown by 150KB.",[1923,1980,1982,1985],{"className":1981},[1926],[1928,1983,1984],{},"Does Vite's dev server performance matter for Core Web Vitals?",[15,1986,1987,1988,1991,1992,1995],{},"No. The dev server serves unbundled modules with on-demand transforms, which is optimised for fast feedback while editing, not for page-load performance. Users only ever receive the production build. Measure, budget and debug performance exclusively against ",[28,1989,1990],{},"vite build"," output served by ",[28,1993,1994],{},"vite preview"," or your real hosting; dev-server timings are misleading in both directions.",[186,1997,1999],{"id":1998},"guides-in-this-topic","Guides in This Topic",[194,2001,2002,2007,2014,2020],{},[197,2003,2004,2006],{},[19,2005,1019],{"href":1018}," — group vendor code for caching without over-splitting.",[197,2008,2009,2013],{},[19,2010,2012],{"href":2011},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fvite-and-rollup-build-optimization\u002Fanalyzing-vite-bundles-with-rollup-plugin-visualizer\u002F","Analyzing Vite bundles with rollup-plugin-visualizer"," — read the treemap and act on it.",[197,2015,2016,2019],{},[19,2017,2018],{"href":1029},"Controlling Vite modulepreload for dynamic imports"," — flatten request chains without over-preloading.",[197,2021,2022,2026],{},[19,2023,2025],{"href":2024},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fvite-and-rollup-build-optimization\u002Fbuilding-tree-shakeable-libraries-with-vite-library-mode\u002F","Building tree-shakeable libraries with Vite library mode"," — ship packages consumers can trim.",[186,2028,2030],{"id":2029},"related","Related",[194,2032,2033,2040,2047,2054],{},[197,2034,2035,2039],{},[19,2036,2038],{"href":2037},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002Fvite-vs-webpack-bundle-splitting-performance\u002F","Vite vs Webpack bundle splitting performance"," — how the two bundlers' defaults compare.",[197,2041,2042,2046],{},[19,2043,2045],{"href":2044},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002F","Dynamic imports and route-based splitting"," — the splitting points Vite turns into chunks.",[197,2048,2049,2053],{},[19,2050,2052],{"href":2051},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fjavascript-performance-budgets\u002F","JavaScript performance budgets"," — enforcing the results in CI.",[197,2055,2056,2060],{},[19,2057,2059],{"href":2058},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fpolyfills-and-differential-serving\u002F","Polyfills and differential serving"," — choosing build targets and polyfills.",[2062,2063,2065],"script",{"type":2064},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"Vite & Rollup Build Optimization: Shaping What Ships to the Browser\",\n  \"description\": \"How Vite's Rollup-based production build decides what ships, and how to tune chunking, preloading and targets for faster LCP and INP.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Environment Setup: Make the Build Observable\",\n      \"text\": \"Emit a stats treemap and a manifest on every build so changes in chunking are visible in pull requests.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Capture a Baseline per Route\",\n      \"text\": \"For each key route, record which chunks load on first navigation, their sizes, and the request chain depth.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Isolate the Bottleneck\",\n      \"text\": \"Open the treemap and look for three patterns: a large library inside the entry or a shared chunk that only one route uses; the same library appearing in several chunks (duplication); and a vendor chunk that contains both stable libraries and frequently updated internal packages.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Apply the Fix\",\n      \"text\": \"The highest-leverage changes, in typical order of impact:\"\n    }\n  ]\n}\n",[2062,2067,2068],{"type":2064},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"Vite & Rollup Build Optimization: Shaping What Ships to the Browser\",\n  \"description\": \"How Vite's Rollup-based production build decides what ships, and how to tune chunking, preloading and targets for faster LCP and INP.\",\n  \"datePublished\": \"2026-10-06\",\n  \"dateModified\": \"2026-10-06\",\n  \"author\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"publisher\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"mainEntityOfPage\": {\n    \"@type\": \"WebPage\",\n    \"@id\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fvite-and-rollup-build-optimization\u002F\"\n  }\n}\n",[2062,2070,2071],{"type":2064},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"BreadcrumbList\",\n  \"itemListElement\": [\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 1,\n      \"name\": \"Home\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 2,\n      \"name\": \"JavaScript Bundle Optimization & Code Splitting\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 3,\n      \"name\": \"Vite & Rollup Build Optimization\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fvite-and-rollup-build-optimization\u002F\"\n    }\n  ]\n}\n",[2073,2074,2075],"style",{},"html pre.shiki code .sjfSM, html code.shiki .sjfSM{--shiki-default:#66707B;--shiki-dark:#BDC4CC;--shiki-light:#66707B}html pre.shiki code .sPARh, html code.shiki .sPARh{--shiki-default:#A0111F;--shiki-dark:#FF9492;--shiki-light:#A0111F}html pre.shiki code .saISM, html code.shiki .saISM{--shiki-default:#0E1116;--shiki-dark:#F0F3F6;--shiki-light:#0E1116}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}html pre.shiki code .smZ65, html code.shiki .smZ65{--shiki-default:#622CBC;--shiki-dark:#DBB7FF;--shiki-light:#622CBC}html pre.shiki code .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}html pre.shiki code .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}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 .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);}",{"title":380,"searchDepth":393,"depth":393,"links":2077},[2078,2079,2080,2081,2082,2083,2084,2085,2086,2087,2088,2089,2090,2091,2092],{"id":188,"depth":393,"text":189},{"id":320,"depth":393,"text":321},{"id":369,"depth":393,"text":370},{"id":571,"depth":393,"text":572},{"id":866,"depth":393,"text":867},{"id":993,"depth":393,"text":994},{"id":1045,"depth":393,"text":1046},{"id":1254,"depth":393,"text":1255},{"id":1308,"depth":393,"text":1309},{"id":1688,"depth":393,"text":1689},{"id":1870,"depth":393,"text":1871},{"id":1889,"depth":393,"text":1890},{"id":1920,"depth":393,"text":1921},{"id":1998,"depth":393,"text":1999},{"id":2029,"depth":393,"text":2030},"How Vite’s Rollup-based production build decides what ships, and how to tune chunking, preloading and targets for faster LCP and INP.","md",{"slug":2096,"type":2097,"breadcrumb":2098,"datePublished":2105,"dateModified":2105},"vite-and-rollup-build-optimization","topic",[2099,2102,2103],{"name":2100,"url":2101},"Home","\u002F",{"name":22,"url":21},{"name":5,"url":2104},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fvite-and-rollup-build-optimization\u002F","2026-10-06","\u002Fjavascript-bundle-optimization-code-splitting\u002Fvite-and-rollup-build-optimization",{"title":2108,"description":2109},"Vite & Rollup Build Optimization for Production","Tune Vite and Rollup production builds for Core Web Vitals: chunking strategy, modulepreload, build targets, CSS splitting and bundle analysis, with CI budgets.","javascript-bundle-optimization-code-splitting\u002Fvite-and-rollup-build-optimization\u002Findex","PwIbdZSDYwovO7vd1hP6gw9tKOER9Z4bWMfykHocA-M",[2113,2117],{"title":2114,"path":2115,"stem":2116,"children":-1},"Writing Tree-Shakeable Library Exports","\u002Fjavascript-bundle-optimization-code-splitting\u002Ftree-shaking-and-dead-code-elimination\u002Fwriting-tree-shakeable-library-exports","javascript-bundle-optimization-code-splitting\u002Ftree-shaking-and-dead-code-elimination\u002Fwriting-tree-shakeable-library-exports\u002Findex",{"title":2118,"path":2119,"stem":2120,"children":-1},"Analyzing Vite Bundles with rollup-plugin-visualizer","\u002Fjavascript-bundle-optimization-code-splitting\u002Fvite-and-rollup-build-optimization\u002Fanalyzing-vite-bundles-with-rollup-plugin-visualizer","javascript-bundle-optimization-code-splitting\u002Fvite-and-rollup-build-optimization\u002Fanalyzing-vite-bundles-with-rollup-plugin-visualizer\u002Findex",1791308072493]