[{"data":1,"prerenderedAt":1342},["ShallowReactive",2],{"content:\u002Fjavascript-bundle-optimization-code-splitting\u002Fvite-and-rollup-build-optimization\u002Fconfiguring-manualchunks-in-vite":3,"surroundings:\u002Fjavascript-bundle-optimization-code-splitting\u002Fvite-and-rollup-build-optimization\u002Fconfiguring-manualchunks-in-vite":1333},{"id":4,"title":5,"body":6,"description":1314,"extension":1315,"meta":1316,"navigation":401,"path":1327,"seo":1328,"stem":1331,"__hash__":1332},"content\u002Fjavascript-bundle-optimization-code-splitting\u002Fvite-and-rollup-build-optimization\u002Fconfiguring-manualchunks-in-vite\u002Findex.md","Configuring manualChunks in Vite",{"type":7,"value":8,"toc":1299},"minimark",[9,14,38,50,144,149,182,186,199,205,214,224,293,297,302,305,634,637,641,644,716,719,723,726,776,779,911,915,918,922,925,1144,1147,1151,1154,1158,1215,1219,1231,1244,1257,1261,1284,1289,1292,1295],[10,11,13],"h1",{"id":12},"how-to-configure-manualchunks-in-vite-without-over-splitting","How to Configure manualChunks in Vite Without Over-Splitting",[15,16,17,18,23,24,28,29,33,34,37],"p",{},"This guide is part of ",[19,20,22],"a",{"href":21},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fvite-and-rollup-build-optimization\u002F","Vite & Rollup Build Optimization"," in ",[19,25,27],{"href":26},"\u002Fjavascript-bundle-optimization-code-splitting\u002F","JavaScript Bundle Optimization & Code Splitting",". ",[30,31,32],"code",{},"build.rollupOptions.output.manualChunks"," is the single most powerful — and most misused — chunking control in Vite. Used well, it puts slow-changing framework and vendor code into long-lived chunks that returning visitors keep in cache for months, while your frequently deployed application code changes independently. Used badly, it either crams every dependency into one giant ",[30,35,36],{},"vendor.js"," that changes on every release, or splits each package into its own chunk, creating hundreds of tiny requests and evaluation-order surprises.",[15,39,40,41,45,46,49],{},"The goal is a small number of chunks grouped by ",[42,43,44],"em",{},"how often they change"," and ",[42,47,48],{},"where they are used",". Framework runtime changes rarely and is used everywhere; a charting library changes rarely but is used on two routes; your design system changes weekly; application code changes daily.",[15,51,52],{},[53,54,60,61,60,68,60,72,60,75,60,84,60,90,60,98,60,104,60,110,60,114,60,118,60,121,60,127,60,131,60,134,60,137,60,141,60],"svg",{"viewBox":55,"width":56,"role":57,"ariaLabel":58,"style":59},"0 0 760 268","100%","img","Layers of a well-chunked Vite build, from rarely changing framework code to frequently changing application code.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[62,63],"rect",{"className":64,"x":66,"y":66,"width":56,"height":56,"fill":67},[65],"svg-canvas","0","#ffffff",[69,70,71],"title",{},"Chunk groups by update frequency",[73,74,58],"desc",{},[62,76],{"x":77,"y":77,"width":78,"height":79,"rx":80,"fill":81,"stroke":82,"style":83},"1","758","266","10","none","currentColor","stroke-opacity:0.18",[85,86,71],"text",{"x":87,"y":88,"fill":82,"style":89},"28.0","34.0","font-size:16px;font-weight:700",[62,91],{"x":87,"y":92,"width":93,"height":94,"rx":95,"fill":96,"stroke":96,"style":97},"56.0","704.0","40.0","6","#0466c8","fill-opacity:0.14;stroke-opacity:0.9",[85,99,103],{"x":100,"y":101,"fill":82,"style":102},"42.0","80.5","font-size:13px;font-weight:700","framework",[85,105,109],{"x":106,"y":101,"fill":82,"style":107,"textAnchor":108},"157.6","font-size:12px","start","vue \u002F react runtime + router — changes a few times a year, used on every route",[62,111],{"x":87,"y":112,"width":93,"height":94,"rx":95,"fill":82,"stroke":82,"style":113},"106.0","fill-opacity:0.06;stroke-opacity:0.4",[85,115,117],{"x":100,"y":116,"fill":82,"style":102},"130.5","vendor-stable",[85,119,120],{"x":106,"y":116,"fill":82,"style":107,"textAnchor":108},"date, validation and state libraries — pinned versions, used widely",[62,122],{"x":87,"y":123,"width":93,"height":94,"rx":95,"fill":124,"stroke":125,"style":126},"156.0","#ffc300","#b8860b","fill-opacity:0.24;stroke-opacity:0.9",[85,128,130],{"x":100,"y":129,"fill":82,"style":102},"180.5","Lazy heavy libs",[85,132,133],{"x":106,"y":129,"fill":82,"style":107,"textAnchor":108},"charts, editors, maps — left to dynamic imports, loaded per route",[62,135],{"x":87,"y":136,"width":93,"height":94,"rx":95,"fill":96,"stroke":96,"style":97},"206.0",[85,138,140],{"x":100,"y":139,"fill":82,"style":102},"230.5","App code",[85,142,143],{"x":106,"y":139,"fill":82,"style":107,"textAnchor":108},"your components and routes — changes every deploy, split by route automatically",[145,146,148],"h2",{"id":147},"rapid-diagnosis","Rapid Diagnosis",[150,151,152,160,166,176],"ul",{},[153,154,155,159],"li",{},[156,157,158],"strong",{},"Diff chunk filenames across two consecutive deploys."," If the large vendor chunk's hash changes on most releases, cache reuse is poor.",[153,161,162,165],{},[156,163,164],{},"Count chunks loaded on the landing route."," More than roughly 15–20 JavaScript requests on first load suggests over-splitting.",[153,167,168,171,172,175],{},[156,169,170],{},"Look for a giant vendor chunk in the treemap."," A single chunk with everything from ",[30,173,174],{},"node_modules"," — including libraries only one route needs — inflates every page.",[153,177,178,181],{},[156,179,180],{},"Check for duplicated packages"," across chunks; poorly designed manual chunks can force Rollup to duplicate shared modules.",[145,183,185],{"id":184},"root-cause-analysis","Root Cause Analysis",[15,187,188,191,192,195,196,198],{},[156,189,190],{},"1. \"All node_modules into vendor\"."," The most common snippet online returns ",[30,193,194],{},"'vendor'"," for every module in ",[30,197,174],{},". It defeats route splitting for dependencies: a library imported dynamically by one route is pulled into the shared vendor chunk, which every route loads.",[15,200,201,204],{},[156,202,203],{},"2. One chunk per package."," Returning the package name for every module creates a chunk per dependency. HTTP\u002F2 makes many requests cheaper, but each still costs scheduling, headers and per-module evaluation overhead, and dozens of tiny chunks compress worse than a few medium ones.",[15,206,207,210,211,213],{},[156,208,209],{},"3. Internal packages in vendor."," Monorepo packages resolved through ",[30,212,174],{}," (workspaces) land in vendor chunks and change on every deploy, busting the cache for genuinely stable third-party code.",[15,215,216,219,220,223],{},[156,217,218],{},"4. Overriding dynamic-import boundaries."," Assigning a module that is only used behind ",[30,221,222],{},"import()"," to an eagerly loaded manual chunk silently moves it onto the critical path.",[15,225,226],{},[53,227,60,230,60,233,60,236,60,238,60,241,60,243,60,250,60,257,60,262,60,266,60,270,60,274,60,278,60,283,60,287,60],{"viewBox":228,"width":56,"role":57,"ariaLabel":229,"style":59},"0 0 760 189","Bar chart comparing JavaScript requests on first load for three manualChunks strategies on the same application.",[62,231],{"className":232,"x":66,"y":66,"width":56,"height":56,"fill":67},[65],[69,234,235],{},"Landing-route requests and cache reuse by manualChunks strategy",[73,237,229],{},[62,239],{"x":77,"y":77,"width":78,"height":240,"rx":80,"fill":81,"stroke":82,"style":83},"187",[85,242,235],{"x":87,"y":88,"fill":82,"style":89},[85,244,249],{"x":245,"y":246,"fill":82,"style":247,"textAnchor":248},"196.6","70.0","font-size:13px","end","All node_modules → vendor",[62,251],{"x":252,"y":92,"width":253,"height":254,"rx":255,"fill":124,"stroke":125,"style":256},"208.6","39.9","19","3","fill-opacity:0.7;stroke-opacity:0.9",[85,258,261],{"x":259,"y":246,"fill":82,"style":260},"254.5","font-size:12px;font-weight:600","4 req",[85,263,265],{"x":245,"y":264,"fill":82,"style":247,"textAnchor":248},"101.0","One chunk per package",[62,267],{"x":252,"y":268,"width":269,"height":254,"rx":255,"fill":124,"stroke":125,"style":256},"87.0","459.4",[85,271,273],{"x":272,"y":264,"fill":82,"style":260},"674.0","46 req",[85,275,277],{"x":245,"y":276,"fill":82,"style":247,"textAnchor":248},"132.0","Grouped by update frequency",[62,279],{"x":252,"y":280,"width":281,"height":254,"rx":255,"fill":96,"stroke":96,"style":282},"118.0","89.9","fill-opacity:0.55;stroke-opacity:0.9",[85,284,286],{"x":285,"y":276,"fill":82,"style":260},"304.5","9 req",[85,288,292],{"x":87,"y":289,"fill":290,"style":291},"167.0","#51617a","font-size:12.5px","The single vendor chunk has few requests but is re-downloaded on most deploys; per-package chunks fragment the load.",[145,294,296],{"id":295},"step-by-step-resolution","Step-by-Step Resolution",[298,299,301],"h3",{"id":300},"_1-group-only-eagerly-used-stable-dependencies","1. Group only eagerly used, stable dependencies",[15,303,304],{},"Name an explicit allow-list of packages that are used on most routes and change rarely. Everything else keeps Vite's default behaviour.",[306,307,312],"pre",{"className":308,"code":309,"language":310,"meta":311,"style":311},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u002F\u002F vite.config.js\nconst FRAMEWORK = ['vue', 'vue-router', '@vue\u002F', 'pinia'];\nconst STABLE = ['date-fns', 'zod', 'nanoid', '@floating-ui\u002F'];\n\nexport default {\n  build: {\n    rollupOptions: {\n      output: {\n        manualChunks(id) {\n          if (!id.includes('node_modules')) return;               \u002F\u002F app code: default splitting\n          if (FRAMEWORK.some((p) => id.includes(`\u002Fnode_modules\u002F${p}`))) return 'framework';\n          if (STABLE.some((p) => id.includes(`\u002Fnode_modules\u002F${p}`))) return 'vendor-stable';\n          \u002F\u002F everything else: let Rollup decide, so lazy-only deps stay lazy\n        },\n      },\n    },\n  },\n};\n\u002F\u002F trade-off: allow-lists need maintenance when you add widely used libraries.\n\u002F\u002F Review them when the treemap shows a new package in several route chunks.\n","javascript","",[30,313,314,323,364,396,403,416,422,428,434,450,485,538,581,587,593,599,605,614,622,628],{"__ignoreMap":311},[315,316,319],"span",{"class":317,"line":318},"line",1,[315,320,322],{"class":321},"sjfSM","\u002F\u002F vite.config.js\n",[315,324,326,330,334,337,341,345,348,351,353,356,358,361],{"class":317,"line":325},2,[315,327,329],{"class":328},"sPARh","const",[315,331,333],{"class":332},"sPXB4"," FRAMEWORK",[315,335,336],{"class":328}," =",[315,338,340],{"class":339},"saISM"," [",[315,342,344],{"class":343},"sZ8jY","'vue'",[315,346,347],{"class":339},", ",[315,349,350],{"class":343},"'vue-router'",[315,352,347],{"class":339},[315,354,355],{"class":343},"'@vue\u002F'",[315,357,347],{"class":339},[315,359,360],{"class":343},"'pinia'",[315,362,363],{"class":339},"];\n",[315,365,367,369,372,374,376,379,381,384,386,389,391,394],{"class":317,"line":366},3,[315,368,329],{"class":328},[315,370,371],{"class":332}," STABLE",[315,373,336],{"class":328},[315,375,340],{"class":339},[315,377,378],{"class":343},"'date-fns'",[315,380,347],{"class":339},[315,382,383],{"class":343},"'zod'",[315,385,347],{"class":339},[315,387,388],{"class":343},"'nanoid'",[315,390,347],{"class":339},[315,392,393],{"class":343},"'@floating-ui\u002F'",[315,395,363],{"class":339},[315,397,399],{"class":317,"line":398},4,[315,400,402],{"emptyLinePlaceholder":401},true,"\n",[315,404,406,409,412],{"class":317,"line":405},5,[315,407,408],{"class":328},"export",[315,410,411],{"class":328}," default",[315,413,415],{"class":414},"sQw3B"," {\n",[315,417,419],{"class":317,"line":418},6,[315,420,421],{"class":339},"  build: {\n",[315,423,425],{"class":317,"line":424},7,[315,426,427],{"class":339},"    rollupOptions: {\n",[315,429,431],{"class":317,"line":430},8,[315,432,433],{"class":339},"      output: {\n",[315,435,437,441,444,447],{"class":317,"line":436},9,[315,438,440],{"class":439},"smZ65","        manualChunks",[315,442,443],{"class":339},"(",[315,445,446],{"class":414},"id",[315,448,449],{"class":339},") {\n",[315,451,453,456,459,462,465,468,470,473,476,479,482],{"class":317,"line":452},10,[315,454,455],{"class":328},"          if",[315,457,458],{"class":339}," (",[315,460,461],{"class":328},"!",[315,463,464],{"class":339},"id.",[315,466,467],{"class":439},"includes",[315,469,443],{"class":339},[315,471,472],{"class":343},"'node_modules'",[315,474,475],{"class":339},")) ",[315,477,478],{"class":328},"return",[315,480,481],{"class":339},";               ",[315,483,484],{"class":321},"\u002F\u002F app code: default splitting\n",[315,486,488,490,492,495,498,501,504,506,509,512,515,517,519,522,524,527,530,532,535],{"class":317,"line":487},11,[315,489,455],{"class":328},[315,491,458],{"class":339},[315,493,494],{"class":332},"FRAMEWORK",[315,496,497],{"class":339},".",[315,499,500],{"class":439},"some",[315,502,503],{"class":339},"((",[315,505,15],{"class":414},[315,507,508],{"class":339},") ",[315,510,511],{"class":328},"=>",[315,513,514],{"class":339}," id.",[315,516,467],{"class":439},[315,518,443],{"class":339},[315,520,521],{"class":343},"`\u002Fnode_modules\u002F${",[315,523,15],{"class":339},[315,525,526],{"class":343},"}`",[315,528,529],{"class":339},"))) ",[315,531,478],{"class":328},[315,533,534],{"class":343}," 'framework'",[315,536,537],{"class":339},";\n",[315,539,541,543,545,548,550,552,554,556,558,560,562,564,566,568,570,572,574,576,579],{"class":317,"line":540},12,[315,542,455],{"class":328},[315,544,458],{"class":339},[315,546,547],{"class":332},"STABLE",[315,549,497],{"class":339},[315,551,500],{"class":439},[315,553,503],{"class":339},[315,555,15],{"class":414},[315,557,508],{"class":339},[315,559,511],{"class":328},[315,561,514],{"class":339},[315,563,467],{"class":439},[315,565,443],{"class":339},[315,567,521],{"class":343},[315,569,15],{"class":339},[315,571,526],{"class":343},[315,573,529],{"class":339},[315,575,478],{"class":328},[315,577,578],{"class":343}," 'vendor-stable'",[315,580,537],{"class":339},[315,582,584],{"class":317,"line":583},13,[315,585,586],{"class":321},"          \u002F\u002F everything else: let Rollup decide, so lazy-only deps stay lazy\n",[315,588,590],{"class":317,"line":589},14,[315,591,592],{"class":339},"        },\n",[315,594,596],{"class":317,"line":595},15,[315,597,598],{"class":339},"      },\n",[315,600,602],{"class":317,"line":601},16,[315,603,604],{"class":339},"    },\n",[315,606,608,611],{"class":317,"line":607},17,[315,609,610],{"class":339},"  }",[315,612,613],{"class":414},",\n",[315,615,617,620],{"class":317,"line":616},18,[315,618,619],{"class":414},"}",[315,621,537],{"class":339},[315,623,625],{"class":317,"line":624},19,[315,626,627],{"class":321},"\u002F\u002F trade-off: allow-lists need maintenance when you add widely used libraries.\n",[315,629,631],{"class":317,"line":630},20,[315,632,633],{"class":321},"\u002F\u002F Review them when the treemap shows a new package in several route chunks.\n",[15,635,636],{},"Expected outcome: two long-lived shared chunks plus route chunks; heavy libraries used only by lazy routes remain in those routes' chunks.",[298,638,640],{"id":639},"_2-keep-internal-workspace-packages-out-of-vendor-chunks","2. Keep internal workspace packages out of vendor chunks",[15,642,643],{},"Detect monorepo packages (they resolve through symlinks to your repository, or have a known scope) and leave them with application code, which changes at the same cadence.",[306,645,647],{"className":308,"code":646,"language":310,"meta":311,"style":311},"manualChunks(id) {\n  if (id.includes('\u002Fpackages\u002F') || id.includes('\u002Fnode_modules\u002F@acme\u002F')) return;   \u002F\u002F internal: app cadence\n  \u002F\u002F ...framework \u002F stable groups as above\n}\n\u002F\u002F trade-off: if an internal design-system package is large and changes rarely,\n\u002F\u002F it may deserve its own chunk. Measure its release cadence before deciding.\n",[30,648,649,657,696,701,706,711],{"__ignoreMap":311},[315,650,651,654],{"class":317,"line":318},[315,652,653],{"class":439},"manualChunks",[315,655,656],{"class":339},"(id) {\n",[315,658,659,662,665,667,669,672,674,677,679,681,683,686,688,690,693],{"class":317,"line":325},[315,660,661],{"class":328},"  if",[315,663,664],{"class":339}," (id.",[315,666,467],{"class":439},[315,668,443],{"class":339},[315,670,671],{"class":343},"'\u002Fpackages\u002F'",[315,673,508],{"class":339},[315,675,676],{"class":328},"||",[315,678,514],{"class":339},[315,680,467],{"class":439},[315,682,443],{"class":339},[315,684,685],{"class":343},"'\u002Fnode_modules\u002F@acme\u002F'",[315,687,475],{"class":339},[315,689,478],{"class":328},[315,691,692],{"class":339},";   ",[315,694,695],{"class":321},"\u002F\u002F internal: app cadence\n",[315,697,698],{"class":317,"line":366},[315,699,700],{"class":321},"  \u002F\u002F ...framework \u002F stable groups as above\n",[315,702,703],{"class":317,"line":398},[315,704,705],{"class":339},"}\n",[315,707,708],{"class":317,"line":405},[315,709,710],{"class":321},"\u002F\u002F trade-off: if an internal design-system package is large and changes rarely,\n",[315,712,713],{"class":317,"line":418},[315,714,715],{"class":321},"\u002F\u002F it may deserve its own chunk. Measure its release cadence before deciding.\n",[15,717,718],{},"Expected outcome: deploying a change to the design system no longer invalidates the framework or vendor-stable chunks.",[298,720,722],{"id":721},"_3-leave-heavy-route-specific-libraries-to-dynamic-imports","3. Leave heavy route-specific libraries to dynamic imports",[15,724,725],{},"Do not assign charting, editor, map or PDF libraries to a manual chunk unless they are used eagerly. Import them where they are used:",[306,727,729],{"className":308,"code":728,"language":310,"meta":311,"style":311},"\u002F\u002F Chart.vue — the library loads only when this component mounts.\nconst { Chart } = await import('chart.js\u002Fauto');\n\u002F\u002F trade-off: the first render of the chart waits for the chunk. Prefetch it on\n\u002F\u002F hover of the tab that shows the chart, or when the route's data starts loading.\n",[30,730,731,736,766,771],{"__ignoreMap":311},[315,732,733],{"class":317,"line":318},[315,734,735],{"class":321},"\u002F\u002F Chart.vue — the library loads only when this component mounts.\n",[315,737,738,740,743,746,749,752,755,758,760,763],{"class":317,"line":325},[315,739,329],{"class":328},[315,741,742],{"class":339}," { ",[315,744,745],{"class":332},"Chart",[315,747,748],{"class":339}," } ",[315,750,751],{"class":328},"=",[315,753,754],{"class":328}," await",[315,756,757],{"class":328}," import",[315,759,443],{"class":339},[315,761,762],{"class":343},"'chart.js\u002Fauto'",[315,764,765],{"class":339},");\n",[315,767,768],{"class":317,"line":366},[315,769,770],{"class":321},"\u002F\u002F trade-off: the first render of the chart waits for the chunk. Prefetch it on\n",[315,772,773],{"class":317,"line":398},[315,774,775],{"class":321},"\u002F\u002F hover of the tab that shows the chart, or when the route's data starts loading.\n",[15,777,778],{},"Expected outcome: the landing route stops paying for libraries it never runs.",[15,780,781],{},[53,782,60,785,60,788,60,791,60,793,60,810,60,813,60,815,60,819,60,825,60,828,60,832,60,840,60,846,60,850,60,855,60,858,60,862,60,864,60,867,60,870,60,873,60,876,60,879,60,882,60,886,60,888,60,891,60,894,60,897,60,901,60,904,60,907,60],{"viewBox":783,"width":56,"role":57,"ariaLabel":784,"style":59},"0 0 760 325","Decision sequence for whether a dependency belongs in a named manual chunk or should be left to Vite's default splitting.",[62,786],{"className":787,"x":66,"y":66,"width":56,"height":56,"fill":67},[65],[69,789,790],{},"Should this package get a manual chunk?",[73,792,784],{},[794,795,796],"defs",{},[797,798,805],"marker",{"id":799,"viewBox":800,"refX":801,"refY":802,"markerWidth":803,"markerHeight":803,"orient":804},"fa5c764ee5","0 0 10 10","9","5","7","auto-start-reverse",[806,807],"path",{"d":808,"fill":82,"style":809},"M0 0 L10 5 L0 10 z","fill-opacity:0.7",[62,811],{"x":77,"y":77,"width":78,"height":812,"rx":80,"fill":81,"stroke":82,"style":83},"323",[85,814,790],{"x":87,"y":88,"fill":82,"style":89},[62,816],{"x":87,"y":92,"width":817,"height":818,"rx":95,"fill":82,"stroke":82,"style":113},"320.0","35.0",[85,820,824],{"x":821,"y":822,"fill":82,"style":102,"textAnchor":823},"188.0","78.0","middle","Used on most routes at startup?",[62,826],{"x":827,"y":92,"width":817,"height":818,"rx":95,"fill":96,"stroke":96,"style":97},"412.0",[85,829,831],{"x":830,"y":822,"fill":82,"style":291,"textAnchor":823},"572.0","Group it: framework or vendor-stable",[317,833],{"x1":834,"y1":835,"x2":836,"y2":835,"stroke":82,"strokeWidth":837,"style":838,"markerEnd":839},"348.0","73.5","410.0","1.5","stroke-opacity:0.6","url(#fa5c764ee5)",[85,841,845],{"x":842,"y":843,"fill":96,"style":844,"textAnchor":823},"380.0","66.5","font-size:11.5px;font-weight:700","yes",[317,847],{"x1":821,"y1":848,"x2":821,"y2":849,"stroke":82,"strokeWidth":837,"style":838,"markerEnd":839},"91.0","125.0",[85,851,854],{"x":852,"y":853,"fill":290,"style":844},"196.0","113.0","no",[62,856],{"x":87,"y":857,"width":817,"height":818,"rx":95,"fill":82,"stroke":82,"style":113},"127.0",[85,859,861],{"x":821,"y":860,"fill":82,"style":102,"textAnchor":823},"149.0","Your own package that changes often?",[62,863],{"x":827,"y":857,"width":817,"height":818,"rx":95,"fill":96,"stroke":96,"style":97},[85,865,866],{"x":830,"y":860,"fill":82,"style":291,"textAnchor":823},"Leave it with application code",[317,868],{"x1":834,"y1":869,"x2":836,"y2":869,"stroke":82,"strokeWidth":837,"style":838,"markerEnd":839},"144.5",[85,871,845],{"x":842,"y":872,"fill":96,"style":844,"textAnchor":823},"137.5",[317,874],{"x1":821,"y1":875,"x2":821,"y2":852,"stroke":82,"strokeWidth":837,"style":838,"markerEnd":839},"162.0",[85,877,854],{"x":852,"y":878,"fill":290,"style":844},"184.0",[62,880],{"x":87,"y":881,"width":817,"height":818,"rx":95,"fill":82,"stroke":82,"style":113},"198.0",[85,883,885],{"x":821,"y":884,"fill":82,"style":102,"textAnchor":823},"220.0","Heavy and used on one or two routes?",[62,887],{"x":827,"y":881,"width":817,"height":818,"rx":95,"fill":96,"stroke":96,"style":97},[85,889,890],{"x":830,"y":884,"fill":82,"style":291,"textAnchor":823},"Dynamic import at the usage site",[317,892],{"x1":834,"y1":893,"x2":836,"y2":893,"stroke":82,"strokeWidth":837,"style":838,"markerEnd":839},"215.5",[85,895,845],{"x":842,"y":896,"fill":96,"style":844,"textAnchor":823},"208.5",[317,898],{"x1":821,"y1":899,"x2":821,"y2":900,"stroke":82,"strokeWidth":837,"style":838,"markerEnd":839},"233.0","267.0",[85,902,854],{"x":852,"y":903,"fill":290,"style":844},"255.0",[62,905],{"x":87,"y":906,"width":817,"height":88,"rx":95,"fill":124,"stroke":125,"style":126},"269.0",[85,908,910],{"x":821,"y":909,"fill":82,"style":291,"textAnchor":823},"290.5","Leave it to Vite's automatic splitting",[298,912,914],{"id":913},"_4-check-evaluation-order-and-duplication","4. Check evaluation order and duplication",[15,916,917],{},"After changing chunking, run the app and check for runtime errors caused by initialisation order (a module evaluated before a dependency it expects) and inspect the treemap for any package now appearing in more than one chunk.",[298,919,921],{"id":920},"_5-measure-cache-reuse-across-releases","5. Measure cache reuse across releases",[15,923,924],{},"Compare filenames between two builds of consecutive commits; the share of bytes in unchanged files is the share returning users keep.",[306,926,930],{"className":927,"code":928,"language":929,"meta":311,"style":311},"language-bash shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","# Build two consecutive commits and compare emitted JS by name and size.\ngit stash -u && git checkout HEAD~1 && npm ci && npx vite build --outDir \u002Ftmp\u002Fprev && git checkout - && git stash pop\nnpx vite build\ncomm -12 \u003C(ls \u002Ftmp\u002Fprev\u002Fassets\u002F*.js | xargs -n1 basename | sort) \u003C(ls dist\u002Fassets\u002F*.js | xargs -n1 basename | sort) \\\n  | while read f; do stat -c %s \"dist\u002Fassets\u002F$f\"; done | awk '{s+=$1} END {print s \" bytes unchanged\"}'\n# trade-off: one pair of commits is a single sample. Track the figure over a few\n# weeks of real deploys before judging the strategy.\n","bash",[30,931,932,937,1002,1011,1082,1134,1139],{"__ignoreMap":311},[315,933,934],{"class":317,"line":318},[315,935,936],{"class":321},"# Build two consecutive commits and compare emitted JS by name and size.\n",[315,938,939,942,945,948,951,953,956,959,961,964,967,969,972,975,978,981,984,986,988,990,993,995,997,999],{"class":317,"line":325},[315,940,941],{"class":414},"git",[315,943,944],{"class":343}," stash",[315,946,947],{"class":332}," -u",[315,949,950],{"class":339}," && ",[315,952,941],{"class":414},[315,954,955],{"class":343}," checkout",[315,957,958],{"class":343}," HEAD~1",[315,960,950],{"class":339},[315,962,963],{"class":414},"npm",[315,965,966],{"class":343}," ci",[315,968,950],{"class":339},[315,970,971],{"class":414},"npx",[315,973,974],{"class":343}," vite",[315,976,977],{"class":343}," build",[315,979,980],{"class":332}," --outDir",[315,982,983],{"class":343}," \u002Ftmp\u002Fprev",[315,985,950],{"class":339},[315,987,941],{"class":414},[315,989,955],{"class":343},[315,991,992],{"class":343}," -",[315,994,950],{"class":339},[315,996,941],{"class":414},[315,998,944],{"class":343},[315,1000,1001],{"class":343}," pop\n",[315,1003,1004,1006,1008],{"class":317,"line":366},[315,1005,971],{"class":414},[315,1007,974],{"class":343},[315,1009,1010],{"class":343}," build\n",[315,1012,1013,1016,1019,1022,1025,1028,1031,1034,1037,1040,1043,1046,1048,1051,1054,1056,1058,1061,1063,1065,1067,1069,1071,1073,1075,1077,1079],{"class":317,"line":398},[315,1014,1015],{"class":414},"comm",[315,1017,1018],{"class":332}," -12",[315,1020,1021],{"class":343}," \u003C(",[315,1023,1024],{"class":414},"ls",[315,1026,1027],{"class":343}," \u002Ftmp\u002Fprev\u002Fassets\u002F",[315,1029,1030],{"class":332},"*",[315,1032,1033],{"class":343},".js ",[315,1035,1036],{"class":328},"|",[315,1038,1039],{"class":414}," xargs",[315,1041,1042],{"class":332}," -n1",[315,1044,1045],{"class":343}," basename ",[315,1047,1036],{"class":328},[315,1049,1050],{"class":414}," sort",[315,1052,1053],{"class":343},")",[315,1055,1021],{"class":343},[315,1057,1024],{"class":414},[315,1059,1060],{"class":343}," dist\u002Fassets\u002F",[315,1062,1030],{"class":332},[315,1064,1033],{"class":343},[315,1066,1036],{"class":328},[315,1068,1039],{"class":414},[315,1070,1042],{"class":332},[315,1072,1045],{"class":343},[315,1074,1036],{"class":328},[315,1076,1050],{"class":414},[315,1078,1053],{"class":343},[315,1080,1081],{"class":328}," \\\n",[315,1083,1084,1087,1090,1093,1096,1099,1102,1105,1108,1111,1114,1117,1120,1122,1125,1128,1131],{"class":317,"line":405},[315,1085,1086],{"class":328},"  |",[315,1088,1089],{"class":328}," while",[315,1091,1092],{"class":332}," read",[315,1094,1095],{"class":343}," f",[315,1097,1098],{"class":339},"; ",[315,1100,1101],{"class":328},"do",[315,1103,1104],{"class":332}," stat",[315,1106,1107],{"class":332}," -c",[315,1109,1110],{"class":343}," %s",[315,1112,1113],{"class":343}," \"dist\u002Fassets\u002F",[315,1115,1116],{"class":339},"$f",[315,1118,1119],{"class":343},"\"",[315,1121,1098],{"class":339},[315,1123,1124],{"class":328},"done",[315,1126,1127],{"class":328}," |",[315,1129,1130],{"class":414}," awk",[315,1132,1133],{"class":343}," '{s+=$1} END {print s \" bytes unchanged\"}'\n",[315,1135,1136],{"class":317,"line":418},[315,1137,1138],{"class":321},"# trade-off: one pair of commits is a single sample. Track the figure over a few\n",[315,1140,1141],{"class":317,"line":424},[315,1142,1143],{"class":321},"# weeks of real deploys before judging the strategy.\n",[15,1145,1146],{},"Expected outcome: a majority of JavaScript bytes unchanged across routine deploys — commonly 80–90% with a frequency-based strategy.",[145,1148,1150],{"id":1149},"verification","Verification",[15,1152,1153],{},"Confirm three things in the built preview: the landing route loads a modest number of chunks (single digits to low teens), heavy libraries appear only when their routes are visited, and the framework and vendor-stable chunk hashes stay identical across a deploy that only changes application code. Field confirmation comes from RUM: LCP for returning visitors in the days after a deploy should no longer dip relative to the days before.",[145,1155,1157],{"id":1156},"common-mistakes-with-manualchunks","Common Mistakes with manualChunks",[150,1159,1160,1178,1201,1209],{},[153,1161,1162,1165,1166,1169,1170,1173,1174,1177],{},[156,1163,1164],{},"Returning a chunk name for CSS modules."," Assigning ",[30,1167,1168],{},".css"," files to JS chunk names can produce odd CSS ordering; restrict the function to ",[30,1171,1172],{},".js","\u002F",[30,1175,1176],{},".ts","\u002Fframework files or leave styles alone.",[153,1179,1180,60,1183,1186,1187,347,1190,347,1193,1196,1197,1200],{},[156,1181,1182],{},"Matching too broadly.",[30,1184,1185],{},"id.includes('react')"," matches ",[30,1188,1189],{},"react-dom",[30,1191,1192],{},"react-router",[30,1194,1195],{},"react-icons"," and any package with \"react\" in its name. Match on ",[30,1198,1199],{},"\u002Fnode_modules\u002F\u003Cpackage>\u002F"," boundaries.",[153,1202,1203,60,1206,1208],{},[156,1204,1205],{},"Forgetting SSR builds.",[30,1207,653],{}," in an SSR config affects the server bundle too, where chunking matters far less; configure it only for the client build if your framework allows.",[153,1210,1211,1214],{},[156,1212,1213],{},"Never revisiting the list."," Dependencies change. Put a reminder in your quarterly performance review to check the treemap against the allow-lists.",[145,1216,1218],{"id":1217},"faq","FAQ",[1220,1221,1224,1228],"details",{"className":1222},[1223],"faq-item",[1225,1226,1227],"summary",{},"Does Vite's default already split vendor code?",[15,1229,1230],{},"Modern Vite versions do not create a single vendor chunk by default; shared dependencies end up in chunks determined by the import graph. That is good for first-load size but can produce shared chunks whose contents (and hashes) shift as the graph changes. A small, explicit framework group stabilises the most widely shared code.",[1220,1232,1234,1237],{"className":1233},[1223],[1225,1235,1236],{},"What about Rolldown's advancedChunks?",[15,1238,1239,1240,1243],{},"Rolldown-based Vite versions offer ",[30,1241,1242],{},"advancedChunks"," with group rules (name, test, priority, size limits). The strategy in this guide translates directly: define groups for framework and stable vendor code with tests on package paths, and leave everything else to the defaults.",[1220,1245,1247,1250],{"className":1246},[1223],[1225,1248,1249],{},"Is a separate chunk for polyfills worth it?",[15,1251,1252,1253,497],{},"If you ship polyfills at all, yes — they change rarely and are needed only by older browsers. Better still is not shipping them to modern browsers; see ",[19,1254,1256],{"href":1255},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fpolyfills-and-differential-serving\u002Fremoving-unneeded-polyfills-with-browserslist\u002F","removing unneeded polyfills with Browserslist",[145,1258,1260],{"id":1259},"related","Related",[150,1262,1263,1270,1277],{},[153,1264,1265,1269],{},[19,1266,1268],{"href":1267},"\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"," — seeing the effect of each change.",[153,1271,1272,1276],{},[19,1273,1275],{"href":1274},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fwebpack-bundle-analysis-techniques\u002Ftuning-splitchunks-cachegroups-for-long-term-caching\u002F","Tuning splitChunks cacheGroups for long-term caching"," — the webpack equivalent.",[153,1278,1279,1283],{},[19,1280,1282],{"href":1281},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fwebpack-bundle-analysis-techniques\u002Freducing-vendor-chunk-size-in-a-react-app\u002F","Reducing vendor chunk size in a React app"," — shrinking what goes into the shared chunk.",[1285,1286,1288],"script",{"type":1287},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"How to Configure manualChunks in Vite Without Over-Splitting\",\n  \"description\": \"A practical manualChunks strategy that improves cache reuse across deploys without fragmenting the bundle into hundreds of requests.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Group only eagerly used, stable dependencies\",\n      \"text\": \"Name an explicit allow-list of packages that are used on most routes and change rarely.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Keep internal workspace packages out of vendor chunks\",\n      \"text\": \"Detect monorepo packages (they resolve through symlinks to your repository, or have a known scope) and leave them with application code, which changes at the same cadence.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Leave heavy route-specific libraries to dynamic imports\",\n      \"text\": \"Do not assign charting, editor, map or PDF libraries to a manual chunk unless they are used eagerly.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Check evaluation order and duplication\",\n      \"text\": \"After changing chunking, run the app and check for runtime errors caused by initialisation order (a module evaluated before a dependency it expects) and inspect the treemap for any package now appearing in more than one chunk.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 5,\n      \"name\": \"Measure cache reuse across releases\",\n      \"text\": \"Compare filenames between two builds of consecutive commits; the share of bytes in unchanged files is the share returning users keep.\"\n    }\n  ]\n}\n",[1285,1290,1291],{"type":1287},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"How to Configure manualChunks in Vite Without Over-Splitting\",\n  \"description\": \"A practical manualChunks strategy that improves cache reuse across deploys without fragmenting the bundle into hundreds of requests.\",\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\u002Fconfiguring-manualchunks-in-vite\u002F\"\n  }\n}\n",[1285,1293,1294],{"type":1287},"\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      \"@type\": \"ListItem\",\n      \"position\": 4,\n      \"name\": \"Configuring manualChunks in Vite\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fvite-and-rollup-build-optimization\u002Fconfiguring-manualchunks-in-vite\u002F\"\n    }\n  ]\n}\n",[1296,1297,1298],"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 .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}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 .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}html pre.shiki code .smZ65, html code.shiki .smZ65{--shiki-default:#622CBC;--shiki-dark:#DBB7FF;--shiki-light:#622CBC}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":311,"searchDepth":325,"depth":325,"links":1300},[1301,1302,1303,1310,1311,1312,1313],{"id":147,"depth":325,"text":148},{"id":184,"depth":325,"text":185},{"id":295,"depth":325,"text":296,"children":1304},[1305,1306,1307,1308,1309],{"id":300,"depth":366,"text":301},{"id":639,"depth":366,"text":640},{"id":721,"depth":366,"text":722},{"id":913,"depth":366,"text":914},{"id":920,"depth":366,"text":921},{"id":1149,"depth":325,"text":1150},{"id":1156,"depth":325,"text":1157},{"id":1217,"depth":325,"text":1218},{"id":1259,"depth":325,"text":1260},"A practical manualChunks strategy that improves cache reuse across deploys without fragmenting the bundle into hundreds of requests.","md",{"slug":1317,"type":1318,"breadcrumb":1319,"datePublished":1326,"dateModified":1326},"configuring-manualchunks-in-vite","article",[1320,1322,1323,1324],{"name":1321,"url":1173},"Home",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":1325},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fvite-and-rollup-build-optimization\u002Fconfiguring-manualchunks-in-vite\u002F","2026-10-06","\u002Fjavascript-bundle-optimization-code-splitting\u002Fvite-and-rollup-build-optimization\u002Fconfiguring-manualchunks-in-vite",{"title":1329,"description":1330},"Configuring manualChunks in Vite for Better Caching","Use Vite’s manualChunks to separate stable vendor code from fast-changing app code, keep heavy libraries out of the entry, and avoid chunk explosion.","javascript-bundle-optimization-code-splitting\u002Fvite-and-rollup-build-optimization\u002Fconfiguring-manualchunks-in-vite\u002Findex","fUAzwHC2g4NdIb8dKjQNdVUY88dAiLK3PIxezvMPwWs",[1334,1338],{"title":1335,"path":1336,"stem":1337},"Building Tree-Shakeable Libraries with Vite Library Mode","\u002Fjavascript-bundle-optimization-code-splitting\u002Fvite-and-rollup-build-optimization\u002Fbuilding-tree-shakeable-libraries-with-vite-library-mode","javascript-bundle-optimization-code-splitting\u002Fvite-and-rollup-build-optimization\u002Fbuilding-tree-shakeable-libraries-with-vite-library-mode\u002Findex",{"title":1339,"path":1340,"stem":1341},"Controlling Vite modulepreload for Dynamic Imports","\u002Fjavascript-bundle-optimization-code-splitting\u002Fvite-and-rollup-build-optimization\u002Fcontrolling-vite-modulepreload-for-dynamic-imports","javascript-bundle-optimization-code-splitting\u002Fvite-and-rollup-build-optimization\u002Fcontrolling-vite-modulepreload-for-dynamic-imports\u002Findex",1791308079737]