[{"data":1,"prerenderedAt":1050},["ShallowReactive",2],{"content:\u002Fjavascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002Fvite-vs-webpack-bundle-splitting-performance":3,"surroundings:\u002Fjavascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002Fvite-vs-webpack-bundle-splitting-performance":1041},{"id":4,"title":5,"body":6,"description":1020,"extension":1021,"meta":1022,"navigation":1034,"path":1035,"seo":1036,"stem":1039,"__hash__":1040},"content\u002Fjavascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002Fvite-vs-webpack-bundle-splitting-performance\u002Findex.md","Vite vs webpack bundle splitting performance",{"type":7,"value":8,"toc":1011},"minimark",[9,13,28,36,41,154,158,199,420,427,570,576,580,590,598,655,659,670,686,756,760,782,790,848,852,945,951,960,963,967,1002,1007],[10,11,5],"h1",{"id":12},"vite-vs-webpack-bundle-splitting-performance",[14,15,16,17,22,23,27],"p",{},"This comparison sits under the ",[18,19,21],"a",{"href":20},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002F","dynamic imports and route-based splitting"," guide and the broader ",[18,24,26],{"href":25},"\u002Fjavascript-bundle-optimization-code-splitting\u002F","JavaScript bundle optimization and code splitting"," workstream, and it answers one decision: when you care about code-splitting and the shape of your production bundle, do you reach for Vite (Rollup under the hood) or webpack?",[14,29,30,31,35],{},"The honest answer is that both can ship a well-split bundle that keeps your initial route JS under 150KB gzipped and keeps main-thread parse below the 50ms long-task budget. The difference is in the ",[32,33,34],"em",{},"ergonomics, defaults, and build economics"," of getting there. Webpack gives you a deeper, more imperative chunking API and the largest plugin surface in the ecosystem. Vite gives you faster builds, leaner defaults, and a Rollup chunking model that produces fewer surprises — at the cost of fine-grained control when your splitting needs get exotic. This page deconstructs the trade-off along the five axes that actually move bundle metrics, then tells you when to pick which.",[37,38,40],"h2",{"id":39},"comparing-the-two-splitters-at-a-glance","Comparing the two splitters at a glance",[14,42,43],{},[44,45,52,53,52,57,52,61,52,71,52,78,52,85,52,90,52,96,52,101,52,105,52,108,52,112,52,115,52,118,52,122,52,125,52,127,52,131,52,134,52,137,52,141,52,144,52,147,52,150,52],"svg",{"xmlns":46,"viewBox":47,"width":48,"role":49,"ariaLabel":50,"style":51},"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","0 0 760 320","100%","img","Decision matrix comparing Vite and webpack across chunking control, build speed, tree-shaking, dynamic import ergonomics, and output size","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:#001d3d"," ",[54,55,56],"title",{},"Vite vs webpack splitting matrix",[58,59,60],"desc",{},"A five-row matrix scoring Vite (Rollup) and webpack on the criteria that affect code splitting and bundle size.",[62,63],"rect",{"x":64,"y":64,"width":65,"height":66,"rx":67,"fill":68,"stroke":69,"style":70},"1","758","318","10","none","currentColor","stroke-opacity:0.18",[72,73,77],"text",{"x":74,"y":75,"fill":69,"style":76},"24","38","font-size:18px;font-weight:700","Splitting decision matrix",[72,79,84],{"x":80,"y":81,"fill":82,"style":83},"430","68","#0466c8","font-size:13px;font-weight:600;text-anchor:middle","Vite \u002F Rollup",[72,86,89],{"x":87,"y":81,"fill":88,"style":83},"640","#b8860b","webpack",[91,92],"line",{"x1":74,"y1":93,"x2":94,"y2":93,"stroke":69,"style":95},"78","736","stroke-opacity:0.3",[72,97,100],{"x":74,"y":98,"fill":69,"style":99},"108","font-size:13px","Chunking control",[72,102,104],{"x":80,"y":98,"fill":69,"style":103},"font-size:13px;text-anchor:middle","good",[72,106,107],{"x":87,"y":98,"fill":69,"style":103},"deepest",[72,109,111],{"x":74,"y":110,"fill":69,"style":99},"142","Build speed",[72,113,114],{"x":80,"y":110,"fill":69,"style":103},"fastest",[72,116,117],{"x":87,"y":110,"fill":69,"style":103},"slower",[72,119,121],{"x":74,"y":120,"fill":69,"style":99},"176","Tree-shaking",[72,123,124],{"x":80,"y":120,"fill":69,"style":103},"strong",[72,126,124],{"x":87,"y":120,"fill":69,"style":103},[72,128,130],{"x":74,"y":129,"fill":69,"style":99},"210","Dynamic import DX",[72,132,133],{"x":80,"y":129,"fill":69,"style":103},"simplest",[72,135,136],{"x":87,"y":129,"fill":69,"style":103},"flexible",[72,138,140],{"x":74,"y":139,"fill":69,"style":99},"244","Output size",[72,142,143],{"x":80,"y":139,"fill":69,"style":103},"leaner",[72,145,146],{"x":87,"y":139,"fill":69,"style":103},"tunable",[91,148],{"x1":74,"y1":149,"x2":94,"y2":149,"stroke":69,"style":95},"262",[72,151,153],{"x":74,"y":152,"fill":69,"style":99},"292","Both can hit \u003C 150KB initial JS; defaults differ, not the ceiling.",[37,155,157],{"id":156},"chunking-control-imperative-depth-versus-declarative-defaults","Chunking control: imperative depth versus declarative defaults",[14,159,160,161,165,166,169,170,173,174,177,178,181,182,185,186,189,190,193,194,198],{},"Webpack's ",[162,163,164],"code",{},"optimization.splitChunks"," is the most expressive chunking engine in production use. You can split by ",[162,167,168],{},"chunks: 'all'",", set ",[162,171,172],{},"minSize","\u002F",[162,175,176],{},"maxSize"," thresholds, and define ",[162,179,180],{},"cacheGroups"," with regex tests and priorities to carve a ",[162,183,184],{},"react",", a ",[162,187,188],{},"vendor",", and a ",[162,191,192],{},"shared"," chunk apart deliberately. This precision matters when you are fighting a specific cache-churn or ",[18,195,197],{"href":196},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fwebpack-bundle-analysis-techniques\u002Freducing-vendor-chunk-size-in-a-react-app\u002F","reducing an oversized vendor chunk"," and need to pin one volatile dependency into its own long-lived file.",[200,201,206],"pre",{"className":202,"code":203,"language":204,"meta":205,"style":205},"language-js shiki shiki-themes github-light-high-contrast github-light-high-contrast github-light-high-contrast","\u002F\u002F webpack.config.js — deliberate cache groups\nmodule.exports = {\n  optimization: {\n    splitChunks: {\n      chunks: 'all',\n      maxSize: 200_000, \u002F\u002F bytes; cap chunk size for better HTTP\u002F2 parallelism\n      cacheGroups: {\n        react: { test: \u002F[\\\\\u002F]node_modules[\\\\\u002F](react|react-dom)[\\\\\u002F]\u002F, name: 'react', priority: 20 },\n        vendor: { test: \u002F[\\\\\u002F]node_modules[\\\\\u002F]\u002F, name: 'vendor', priority: 10 },\n      },\n    },\n  },\n  \u002F\u002F trade-off: this manual control is powerful but brittle — over-splitting\n  \u002F\u002F creates many tiny chunks whose request overhead can exceed the bytes saved.\n  \u002F\u002F Skip cacheGroups entirely on small apps; webpack's defaults are already fine.\n};\n","js","",[162,207,208,216,237,243,249,262,277,283,343,378,384,390,396,402,408,414],{"__ignoreMap":205},[209,210,212],"span",{"class":91,"line":211},1,[209,213,215],{"class":214},"sIIH1","\u002F\u002F webpack.config.js — deliberate cache groups\n",[209,217,219,223,227,230,234],{"class":91,"line":218},2,[209,220,222],{"class":221},"sf6mN","module",[209,224,226],{"class":225},"syybb",".",[209,228,229],{"class":221},"exports",[209,231,233],{"class":232},"sP5qI"," =",[209,235,236],{"class":225}," {\n",[209,238,240],{"class":91,"line":239},3,[209,241,242],{"class":225},"  optimization: {\n",[209,244,246],{"class":91,"line":245},4,[209,247,248],{"class":225},"    splitChunks: {\n",[209,250,252,255,259],{"class":91,"line":251},5,[209,253,254],{"class":225},"      chunks: ",[209,256,258],{"class":257},"s-_DF","'all'",[209,260,261],{"class":225},",\n",[209,263,265,268,271,274],{"class":91,"line":264},6,[209,266,267],{"class":225},"      maxSize: ",[209,269,270],{"class":221},"200_000",[209,272,273],{"class":225},", ",[209,275,276],{"class":214},"\u002F\u002F bytes; cap chunk size for better HTTP\u002F2 parallelism\n",[209,278,280],{"class":91,"line":279},7,[209,281,282],{"class":225},"      cacheGroups: {\n",[209,284,286,289,292,295,299,302,305,307,309,311,314,317,320,322,324,326,328,331,334,337,340],{"class":91,"line":285},8,[209,287,288],{"class":225},"        react: { test:",[209,290,291],{"class":257}," \u002F",[209,293,294],{"class":221},"[",[209,296,298],{"class":297},"s6uau","\\\\",[209,300,301],{"class":221},"\u002F]",[209,303,304],{"class":257},"node_modules",[209,306,294],{"class":221},[209,308,298],{"class":297},[209,310,301],{"class":221},[209,312,313],{"class":257},"(react",[209,315,316],{"class":232},"|",[209,318,319],{"class":257},"react-dom)",[209,321,294],{"class":221},[209,323,298],{"class":297},[209,325,301],{"class":221},[209,327,173],{"class":257},[209,329,330],{"class":225},", name: ",[209,332,333],{"class":257},"'react'",[209,335,336],{"class":225},", priority: ",[209,338,339],{"class":221},"20",[209,341,342],{"class":225}," },\n",[209,344,346,349,351,353,355,357,359,361,363,365,367,369,372,374,376],{"class":91,"line":345},9,[209,347,348],{"class":225},"        vendor: { test:",[209,350,291],{"class":257},[209,352,294],{"class":221},[209,354,298],{"class":297},[209,356,301],{"class":221},[209,358,304],{"class":257},[209,360,294],{"class":221},[209,362,298],{"class":297},[209,364,301],{"class":221},[209,366,173],{"class":257},[209,368,330],{"class":225},[209,370,371],{"class":257},"'vendor'",[209,373,336],{"class":225},[209,375,67],{"class":221},[209,377,342],{"class":225},[209,379,381],{"class":91,"line":380},10,[209,382,383],{"class":225},"      },\n",[209,385,387],{"class":91,"line":386},11,[209,388,389],{"class":225},"    },\n",[209,391,393],{"class":91,"line":392},12,[209,394,395],{"class":225},"  },\n",[209,397,399],{"class":91,"line":398},13,[209,400,401],{"class":214},"  \u002F\u002F trade-off: this manual control is powerful but brittle — over-splitting\n",[209,403,405],{"class":91,"line":404},14,[209,406,407],{"class":214},"  \u002F\u002F creates many tiny chunks whose request overhead can exceed the bytes saved.\n",[209,409,411],{"class":91,"line":410},15,[209,412,413],{"class":214},"  \u002F\u002F Skip cacheGroups entirely on small apps; webpack's defaults are already fine.\n",[209,415,417],{"class":91,"line":416},16,[209,418,419],{"class":225},"};\n",[14,421,422,423,426],{},"Vite delegates production chunking to Rollup. The default heuristic already splits dynamic imports into their own chunks and hoists shared dependencies sensibly, so most teams never touch it. When you do need control, you reach for ",[162,424,425],{},"build.rollupOptions.output.manualChunks",", which can be an object map or a function that receives each module id.",[200,428,430],{"className":202,"code":429,"language":204,"meta":205,"style":205},"\u002F\u002F vite.config.js — function form gives per-module control\nexport default {\n  build: {\n    rollupOptions: {\n      output: {\n        manualChunks(id) {\n          if (id.includes('node_modules\u002Freact')) return 'react';\n          if (id.includes('node_modules')) return 'vendor';\n          \u002F\u002F trade-off: a function that returns a single 'vendor' bucket can\n          \u002F\u002F produce a circular-dependency warning when a vendor module imports\n          \u002F\u002F app code; prefer the object map form unless you truly need logic.\n        },\n      },\n    },\n  },\n};\n",[162,431,432,437,448,453,458,463,478,506,528,533,538,543,548,552,556,563],{"__ignoreMap":205},[209,433,434],{"class":91,"line":211},[209,435,436],{"class":214},"\u002F\u002F vite.config.js — function form gives per-module control\n",[209,438,439,442,445],{"class":91,"line":218},[209,440,441],{"class":232},"export",[209,443,444],{"class":232}," default",[209,446,236],{"class":447},"seIZK",[209,449,450],{"class":91,"line":239},[209,451,452],{"class":225},"  build: {\n",[209,454,455],{"class":91,"line":245},[209,456,457],{"class":225},"    rollupOptions: {\n",[209,459,460],{"class":91,"line":251},[209,461,462],{"class":225},"      output: {\n",[209,464,465,469,472,475],{"class":91,"line":264},[209,466,468],{"class":467},"ssM3C","        manualChunks",[209,470,471],{"class":225},"(",[209,473,474],{"class":447},"id",[209,476,477],{"class":225},") {\n",[209,479,480,483,486,489,491,494,497,500,503],{"class":91,"line":279},[209,481,482],{"class":232},"          if",[209,484,485],{"class":225}," (id.",[209,487,488],{"class":467},"includes",[209,490,471],{"class":225},[209,492,493],{"class":257},"'node_modules\u002Freact'",[209,495,496],{"class":225},")) ",[209,498,499],{"class":232},"return",[209,501,502],{"class":257}," 'react'",[209,504,505],{"class":225},";\n",[209,507,508,510,512,514,516,519,521,523,526],{"class":91,"line":285},[209,509,482],{"class":232},[209,511,485],{"class":225},[209,513,488],{"class":467},[209,515,471],{"class":225},[209,517,518],{"class":257},"'node_modules'",[209,520,496],{"class":225},[209,522,499],{"class":232},[209,524,525],{"class":257}," 'vendor'",[209,527,505],{"class":225},[209,529,530],{"class":91,"line":345},[209,531,532],{"class":214},"          \u002F\u002F trade-off: a function that returns a single 'vendor' bucket can\n",[209,534,535],{"class":91,"line":380},[209,536,537],{"class":214},"          \u002F\u002F produce a circular-dependency warning when a vendor module imports\n",[209,539,540],{"class":91,"line":386},[209,541,542],{"class":214},"          \u002F\u002F app code; prefer the object map form unless you truly need logic.\n",[209,544,545],{"class":91,"line":392},[209,546,547],{"class":225},"        },\n",[209,549,550],{"class":91,"line":398},[209,551,383],{"class":225},[209,553,554],{"class":91,"line":404},[209,555,389],{"class":225},[209,557,558,561],{"class":91,"line":410},[209,559,560],{"class":225},"  }",[209,562,261],{"class":447},[209,564,565,568],{"class":91,"line":416},[209,566,567],{"class":447},"}",[209,569,505],{"class":225},[14,571,572,573,575],{},"The verdict: webpack wins on raw depth and edge-case control. Vite wins on \"the default is usually right.\" If you have never needed ",[162,574,180],{},", Vite removes a class of configuration you do not want to maintain.",[37,577,579],{"id":578},"build-speed-where-the-daily-cost-actually-lives","Build speed: where the daily cost actually lives",[14,581,582,583,173,586,589],{},"Build speed is the axis where the gap is largest. Vite runs an unbundled, native-ESM dev server backed by esbuild for transforms, so cold starts and hot updates are near-instant regardless of app size. Webpack rebuilds a dependency graph in dev; even with persistent caching and ",[162,584,585],{},"swc",[162,587,588],{},"esbuild-loader",", large apps feel the difference. For production, Vite builds with Rollup (with an esbuild-based transform\u002Fminify path), which is typically faster than a webpack production build of comparable scope, though the gap narrows as plugin counts rise.",[14,591,592,593,597],{},"This matters for your feedback loop more than your shipped bytes. A faster build does not directly improve ",[18,594,596],{"href":595},"\u002Fcore-web-vitals-measurement\u002Foptimizing-first-input-delay-fid\u002F","First Input Delay or INP"," for users, but it shortens the iteration cycle when you are profiling and re-splitting to hit those interaction budgets.",[200,599,603],{"className":600,"code":601,"language":602,"meta":205,"style":205},"language-bash shiki shiki-themes github-light-high-contrast github-light-high-contrast github-light-high-contrast","# Reproducible build-time benchmark (run 3x, take the median)\nrm -rf dist && time npx vite build\nrm -rf dist && time npx webpack --mode production\n# trade-off: wall-clock build time is irrelevant if CI is the bottleneck and\n# already cached — optimize the metric your team actually waits on, not this one.\n","bash",[162,604,605,610,630,645,650],{"__ignoreMap":205},[209,606,607],{"class":91,"line":211},[209,608,609],{"class":214},"# Reproducible build-time benchmark (run 3x, take the median)\n",[209,611,612,615,618,621,624,627],{"class":91,"line":218},[209,613,614],{"class":447},"rm",[209,616,617],{"class":221}," -rf",[209,619,620],{"class":257}," dist",[209,622,623],{"class":225}," && ",[209,625,626],{"class":232},"time",[209,628,629],{"class":225}," npx vite build\n",[209,631,632,634,636,638,640,642],{"class":91,"line":239},[209,633,614],{"class":447},[209,635,617],{"class":221},[209,637,620],{"class":257},[209,639,623],{"class":225},[209,641,626],{"class":232},[209,643,644],{"class":225}," npx webpack --mode production\n",[209,646,647],{"class":91,"line":245},[209,648,649],{"class":214},"# trade-off: wall-clock build time is irrelevant if CI is the bottleneck and\n",[209,651,652],{"class":91,"line":251},[209,653,654],{"class":214},"# already cached — optimize the metric your team actually waits on, not this one.\n",[37,656,658],{"id":657},"tree-shaking-comparable-engines-different-blind-spots","Tree-shaking: comparable engines, different blind spots",[14,660,661,662,665,666,669],{},"Both bundlers do real static tree-shaking on ES modules, and both honor the ",[162,663,664],{},"sideEffects"," field in ",[162,667,668],{},"package.json",". Rollup (Vite) has a historically strong reputation for aggressive dead-code elimination on clean ESM, and its output tends to be flatter with less wrapper boilerplate. Webpack's tree-shaking is equally capable on modern code but is more sensitive to how a dependency declares its module entry point and side effects.",[14,671,672,673,676,677,680,681,685],{},"The blind spots are shared: a CommonJS dependency, a missing ",[162,674,675],{},"sideEffects: false",", or a mis-declared ",[162,678,679],{},"\"module\""," field defeats both. If you are chasing residual dead code, the techniques in ",[18,682,684],{"href":683},"\u002Fjavascript-bundle-optimization-code-splitting\u002Ftree-shaking-and-dead-code-elimination\u002F","tree-shaking and dead code elimination"," apply identically to either bundler — the fix is in the dependency graph, not the bundler choice.",[200,687,689],{"className":202,"code":688,"language":204,"meta":205,"style":205},"\u002F\u002F Both bundlers prune this named import; neither prunes the namespace import.\nimport { debounce } from 'lodash-es';   \u002F\u002F shakeable in Vite AND webpack\n\u002F\u002F import _ from 'lodash';              \u002F\u002F defeats DCE in BOTH bundlers\nconst handler = debounce(onScroll, 150);\n\u002F\u002F trade-off: don't assume switching bundlers fixes bloat — if the dependency\n\u002F\u002F ships CommonJS, you must replace or alias it regardless of Vite vs webpack.\n",[162,690,691,696,716,724,746,751],{"__ignoreMap":205},[209,692,693],{"class":91,"line":211},[209,694,695],{"class":214},"\u002F\u002F Both bundlers prune this named import; neither prunes the namespace import.\n",[209,697,698,701,704,707,710,713],{"class":91,"line":218},[209,699,700],{"class":232},"import",[209,702,703],{"class":225}," { debounce } ",[209,705,706],{"class":232},"from",[209,708,709],{"class":257}," 'lodash-es'",[209,711,712],{"class":225},";   ",[209,714,715],{"class":214},"\u002F\u002F shakeable in Vite AND webpack\n",[209,717,718,721],{"class":91,"line":239},[209,719,720],{"class":214},"\u002F\u002F import _ from 'lodash';",[209,722,723],{"class":214},"              \u002F\u002F defeats DCE in BOTH bundlers\n",[209,725,726,729,732,734,737,740,743],{"class":91,"line":245},[209,727,728],{"class":232},"const",[209,730,731],{"class":221}," handler",[209,733,233],{"class":232},[209,735,736],{"class":467}," debounce",[209,738,739],{"class":225},"(onScroll, ",[209,741,742],{"class":221},"150",[209,744,745],{"class":225},");\n",[209,747,748],{"class":91,"line":251},[209,749,750],{"class":214},"\u002F\u002F trade-off: don't assume switching bundlers fixes bloat — if the dependency\n",[209,752,753],{"class":91,"line":264},[209,754,755],{"class":214},"\u002F\u002F ships CommonJS, you must replace or alias it regardless of Vite vs webpack.\n",[37,757,759],{"id":758},"dynamic-import-ergonomics-and-output-size","Dynamic import ergonomics and output size",[14,761,762,763,766,767,273,770,773,774,777,778,781],{},"For route-based splitting the syntax is identical — ",[162,764,765],{},"import('.\u002FRoute.jsx')"," produces a separate chunk in both. The difference is in the surrounding ergonomics. Webpack supports magic comments (",[162,768,769],{},"\u002F* webpackChunkName: \"settings\" *\u002F",[162,771,772],{},"webpackPrefetch: true",") to name and hint chunks inline, which is a genuine convenience for prefetch tuning. Vite has no magic comments; you control names through ",[162,775,776],{},"manualChunks"," and prefetch through framework-level APIs or ",[162,779,780],{},"\u003Clink rel=\"modulepreload\">"," injection, which Vite does automatically for imported chunks.",[14,783,784,785,789],{},"On output size, Vite\u002FRollup tends to emit slightly smaller bundles out of the box because of flatter scope-hoisted output and lean defaults, while webpack ships a small runtime per build that you can minimize but not fully remove. In practice both land within a few kilobytes of each other once minified with the same minifier; the realistic delta is dwarfed by your dependency choices. Whichever you pick, pair the split with the right ",[18,786,788],{"href":787},"\u002Fjavascript-bundle-optimization-code-splitting\u002Ftree-shaking-and-dead-code-elimination\u002Fesbuild-vs-terser-for-production-minification\u002F","minification choice between esbuild and Terser",", since that decision affects final bytes more than the splitter does.",[200,791,793],{"className":202,"code":792,"language":204,"meta":205,"style":205},"\u002F\u002F vite.config.js — automatic modulepreload keeps split chunks warm\nexport default {\n  build: {\n    modulePreload: { polyfill: true }, \u002F\u002F injects \u003Clink rel=modulepreload> for chunks\n    \u002F\u002F trade-off: preloading every async chunk can over-fetch on low-end devices\n    \u002F\u002F and steal bandwidth from the LCP resource; gate prefetch by route intent.\n  },\n};\n",[162,794,795,800,808,812,826,831,836,842],{"__ignoreMap":205},[209,796,797],{"class":91,"line":211},[209,798,799],{"class":214},"\u002F\u002F vite.config.js — automatic modulepreload keeps split chunks warm\n",[209,801,802,804,806],{"class":91,"line":218},[209,803,441],{"class":232},[209,805,444],{"class":232},[209,807,236],{"class":447},[209,809,810],{"class":91,"line":239},[209,811,452],{"class":225},[209,813,814,817,820,823],{"class":91,"line":245},[209,815,816],{"class":225},"    modulePreload: { polyfill: ",[209,818,819],{"class":221},"true",[209,821,822],{"class":225}," }, ",[209,824,825],{"class":214},"\u002F\u002F injects \u003Clink rel=modulepreload> for chunks\n",[209,827,828],{"class":91,"line":251},[209,829,830],{"class":214},"    \u002F\u002F trade-off: preloading every async chunk can over-fetch on low-end devices\n",[209,832,833],{"class":91,"line":264},[209,834,835],{"class":214},"    \u002F\u002F and steal bandwidth from the LCP resource; gate prefetch by route intent.\n",[209,837,838,840],{"class":91,"line":279},[209,839,560],{"class":225},[209,841,261],{"class":447},[209,843,844,846],{"class":91,"line":285},[209,845,567],{"class":447},[209,847,505],{"class":225},[37,849,851],{"id":850},"when-to-pick-which","When to pick which",[853,854,855,871],"table",{},[856,857,858],"thead",{},[859,860,861,866,869],"tr",{},[862,863,865],"th",{"align":864},"left","Criterion",[862,867,868],{"align":864},"Vite (Rollup)",[862,870,89],{"align":864},[872,873,874,892,903,913,923,934],"tbody",{},[859,875,876,879,885],{},[877,878,100],"td",{"align":864},[877,880,881,882,884],{"align":864},"Good; ",[162,883,776],{}," covers most cases",[877,886,887,888,891],{"align":864},"Deepest; ",[162,889,890],{},"splitChunks.cacheGroups"," for surgical control",[859,893,894,897,900],{},[877,895,896],{"align":864},"Build \u002F dev speed",[877,898,899],{"align":864},"Fastest (native ESM + esbuild)",[877,901,902],{"align":864},"Slower; mitigated by persistent cache",[859,904,905,907,910],{},[877,906,121],{"align":864},[877,908,909],{"align":864},"Strong, flat output",[877,911,912],{"align":864},"Strong, entry-field sensitive",[859,914,915,917,920],{},[877,916,130],{"align":864},[877,918,919],{"align":864},"Simplest; auto modulepreload",[877,921,922],{"align":864},"Flexible; magic comments for naming\u002Fprefetch",[859,924,925,928,931],{},[877,926,927],{"align":864},"Default output size",[877,929,930],{"align":864},"Leaner by default",[877,932,933],{"align":864},"Tunable to parity",[859,935,936,939,942],{},[877,937,938],{"align":864},"Ecosystem \u002F legacy plugins",[877,940,941],{"align":864},"Growing, Rollup-based",[877,943,944],{"align":864},"Largest, most mature",[14,946,947,950],{},[124,948,949],{},"Pick Vite"," for new SPAs and most React\u002FVue apps where build speed and lean defaults matter and your splitting needs are route-shaped. The fast feedback loop pays off every single day, and the defaults keep you out of chunking trouble.",[14,952,953,956,957,959],{},[124,954,955],{},"Pick webpack"," when you need surgical chunk control (long-lived cache groups, ",[162,958,176],{}," tuning, named-chunk prefetch strategies), depend on a webpack-only loader or plugin, or maintain a large existing config where migration risk outweighs the build-speed gain.",[14,961,962],{},"Either way, the bundler is the last 5% of your bundle budget. Your dependency selection, route boundaries, and tree-shaking hygiene decide the other 95% — and those skills transfer between both tools.",[37,964,966],{"id":965},"related","Related",[968,969,970,977,984,990,996],"ul",{},[971,972,973,976],"li",{},[18,974,975],{"href":20},"Dynamic imports and route-based splitting"," — the parent guide on splitting at route boundaries.",[971,978,979,983],{},[18,980,982],{"href":981},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002Fimplementing-route-level-code-splitting-in-nextjs\u002F","Implementing route-level code splitting in Next.js"," — applying these ideas in a webpack-based framework.",[971,985,986,989],{},[18,987,988],{"href":787},"esbuild vs Terser for production minification"," — the minifier choice that affects final bytes more than the splitter.",[971,991,992,995],{},[18,993,994],{"href":196},"Reducing vendor chunk size in a React app"," — a scenario where deep chunk control earns its keep.",[971,997,998,1001],{},[18,999,1000],{"href":595},"Optimizing First Input Delay and INP"," — why smaller, better-split bundles improve interaction responsiveness.",[1003,1004,1006],"script",{"type":1005},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@graph\": [\n    {\n      \"@type\": \"TechArticle\",\n      \"headline\": \"Vite vs webpack bundle splitting performance\",\n      \"description\": \"A decision matrix comparing Vite (Rollup) and webpack for code-splitting and production bundle performance across chunking control, build speed, tree-shaking, dynamic import ergonomics, and output size.\",\n      \"datePublished\": \"2026-06-18\",\n      \"dateModified\": \"2026-06-18\",\n      \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002Fvite-vs-webpack-bundle-splitting-performance\u002F\"\n    },\n    {\n      \"@type\": \"HowTo\",\n      \"name\": \"Choose between Vite and webpack for bundle splitting\",\n      \"step\": [\n        { \"@type\": \"HowToStep\", \"position\": 1, \"name\": \"Score chunking control needs\", \"text\": \"Decide whether default splitting suffices or you need cacheGroups-level surgical control.\" },\n        { \"@type\": \"HowToStep\", \"position\": 2, \"name\": \"Benchmark build speed\", \"text\": \"Run a reproducible production build benchmark three times and take the median.\" },\n        { \"@type\": \"HowToStep\", \"position\": 3, \"name\": \"Verify tree-shaking parity\", \"text\": \"Confirm dependencies ship ESM and declare sideEffects so both bundlers can prune dead code.\" },\n        { \"@type\": \"HowToStep\", \"position\": 4, \"name\": \"Evaluate dynamic import ergonomics\", \"text\": \"Compare magic-comment prefetch hints against automatic modulepreload for your routes.\" },\n        { \"@type\": \"HowToStep\", \"position\": 5, \"name\": \"Compare output size\", \"text\": \"Minify both outputs with the same minifier and compare gzipped initial route JS against the 150KB budget.\" }\n      ]\n    },\n    {\n      \"@type\": \"BreadcrumbList\",\n      \"itemListElement\": [\n        { \"@type\": \"ListItem\", \"position\": 1, \"name\": \"Home\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002F\" },\n        { \"@type\": \"ListItem\", \"position\": 2, \"name\": \"JavaScript Bundle Optimization & Code Splitting\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002F\" },\n        { \"@type\": \"ListItem\", \"position\": 3, \"name\": \"Dynamic Imports & Route-Based Splitting\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002F\" },\n        { \"@type\": \"ListItem\", \"position\": 4, \"name\": \"Vite vs webpack bundle splitting performance\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002Fvite-vs-webpack-bundle-splitting-performance\u002F\" }\n      ]\n    }\n  ]\n}\n",[1008,1009,1010],"style",{},"html pre.shiki code .sIIH1, html code.shiki .sIIH1{--shiki-default:#66707B;--shiki-dark:#66707B;--shiki-light:#66707B}html pre.shiki code .sf6mN, html code.shiki .sf6mN{--shiki-default:#023B95;--shiki-dark:#023B95;--shiki-light:#023B95}html pre.shiki code .syybb, html code.shiki .syybb{--shiki-default:#0E1116;--shiki-dark:#0E1116;--shiki-light:#0E1116}html pre.shiki code .sP5qI, html code.shiki .sP5qI{--shiki-default:#A0111F;--shiki-dark:#A0111F;--shiki-light:#A0111F}html pre.shiki code .s-_DF, html code.shiki .s-_DF{--shiki-default:#032563;--shiki-dark:#032563;--shiki-light:#032563}html pre.shiki code .s6uau, html code.shiki .s6uau{--shiki-default:#024C1A;--shiki-default-font-weight:bold;--shiki-dark:#024C1A;--shiki-dark-font-weight:bold;--shiki-light:#024C1A;--shiki-light-font-weight:bold}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);}html pre.shiki code .seIZK, html code.shiki .seIZK{--shiki-default:#702C00;--shiki-dark:#702C00;--shiki-light:#702C00}html pre.shiki code .ssM3C, html code.shiki .ssM3C{--shiki-default:#622CBC;--shiki-dark:#622CBC;--shiki-light:#622CBC}",{"title":205,"searchDepth":218,"depth":218,"links":1012},[1013,1014,1015,1016,1017,1018,1019],{"id":39,"depth":218,"text":40},{"id":156,"depth":218,"text":157},{"id":578,"depth":218,"text":579},{"id":657,"depth":218,"text":658},{"id":758,"depth":218,"text":759},{"id":850,"depth":218,"text":851},{"id":965,"depth":218,"text":966},"A decision matrix comparing Vite (Rollup) and webpack for code-splitting and production bundle performance.","md",{"slug":12,"type":1023,"breadcrumb":1024,"datePublished":1033,"dateModified":1033},"long_tail",[1025,1027,1029,1031],{"name":1026,"url":173},"Home",{"name":1028,"url":25},"JavaScript Bundle Optimization & Code Splitting",{"name":1030,"url":20},"Dynamic Imports & Route-Based Splitting",{"name":5,"url":1032},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002Fvite-vs-webpack-bundle-splitting-performance\u002F","2026-06-18",true,"\u002Fjavascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002Fvite-vs-webpack-bundle-splitting-performance",{"title":1037,"description":1038},"Vite vs Webpack: Bundle Splitting Performance","Compare Vite (Rollup) and webpack for code-splitting and bundle performance: chunking control, build speed, tree-shaking, dynamic import ergonomics, output size.","javascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002Fvite-vs-webpack-bundle-splitting-performance\u002Findex","TVAgSHMBH0M9syJ2EmJDmeXAfSLfXWKurVP8TK-a5gk",[1042,1046],{"title":1043,"path":1044,"stem":1045,"children":-1},"Route-Level Code Splitting in Next.js","\u002Fjavascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002Fimplementing-route-level-code-splitting-in-nextjs","javascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002Fimplementing-route-level-code-splitting-in-nextjs\u002Findex",{"title":1047,"path":1048,"stem":1049,"children":-1},"Modern Module Formats: ESM vs CommonJS","\u002Fjavascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs","javascript-bundle-optimization-code-splitting\u002Fmodern-module-formats-esm-vs-commonjs\u002Findex",1782237171366]