[{"data":1,"prerenderedAt":1148},["ShallowReactive",2],{"content:\u002Fjavascript-bundle-optimization-code-splitting\u002Fwebpack-bundle-analysis-techniques\u002Freducing-vendor-chunk-size-in-a-react-app\u002F":3,"surroundings:\u002Fjavascript-bundle-optimization-code-splitting\u002Fwebpack-bundle-analysis-techniques\u002Freducing-vendor-chunk-size-in-a-react-app\u002F":1142},{"id":4,"title":5,"body":6,"description":1122,"extension":1123,"meta":1124,"navigation":683,"path":1136,"seo":1137,"stem":1140,"__hash__":1141},"content\u002Fjavascript-bundle-optimization-code-splitting\u002Fwebpack-bundle-analysis-techniques\u002Freducing-vendor-chunk-size-in-a-react-app\u002Findex.md","Reducing vendor chunk size in a React app",{"type":7,"value":8,"toc":1110},"minimark",[9,13,33,40,156,161,164,205,287,291,313,331,337,343,347,352,355,604,607,611,618,782,785,789,792,836,839,843,846,924,938,942,945,978,1061,1064,1068,1101,1106],[10,11,5],"h1",{"id":12},"reducing-vendor-chunk-size-in-a-react-app",[14,15,16,17,22,23,27,28,32],"p",{},"This walkthrough sits beneath the ",[18,19,21],"a",{"href":20},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fwebpack-bundle-analysis-techniques\u002F","webpack bundle analysis techniques"," guide and the broader ",[18,24,26],{"href":25},"\u002Fjavascript-bundle-optimization-code-splitting\u002F","JavaScript bundle optimization and code splitting"," effort, and it tackles a specific, common failure: a React app whose ",[29,30,31],"code",{},"vendor.js"," chunk has ballooned past 400KB gzipped, dragging initial load and inflating main-thread parse beyond the 50ms long-task budget.",[14,34,35,36,39],{},"A bloated vendor chunk is the single most common bundle pathology in React apps, because the default instinct — \"put all of ",[29,37,38],{},"node_modules"," in one chunk\" — quietly accumulates every dependency a single import ever pulled in. The symptom is a slow first paint and a sluggish first interaction; the cause is almost always two or three oversized libraries plus a cache-churn pattern that re-downloads the whole chunk on every deploy. This page moves from baseline measurement to root-cause isolation to numbered, byte-quantified fixes, then verifies the result.",[14,41,42],{},[43,44,51,52,51,56,51,60,51,70,51,77,51,83,51,91,51,97,51,102,51,106,51,111,51,116,51,120,51,127,51,131,51,134,51,138,51,142,51,145,51,151,51],"svg",{"xmlns":45,"viewBox":46,"width":47,"role":48,"ariaLabel":49,"style":50},"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","0 0 760 312","100%","img","Before and after treemap of a 400KB monolithic vendor chunk split into framework, slim vendor, and a lazy route chunk","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:#001d3d"," ",[53,54,55],"title",{},"Splitting a monolithic vendor chunk",[57,58,59],"desc",{},"One 400KB vendor chunk versus a stable framework chunk, a slimmer vendor chunk, and a lazy route-only chunk.",[61,62],"rect",{"x":63,"y":63,"width":64,"height":65,"rx":66,"fill":67,"stroke":68,"style":69},"1","758","310","10","none","currentColor","stroke-opacity:0.18",[71,72,76],"text",{"x":73,"y":74,"fill":68,"style":75},"24","36","font-size:17px;font-weight:700","vendor.js: monolith to cache groups",[71,78,82],{"x":79,"y":80,"fill":68,"style":81},"40","76","font-size:13px;font-weight:600","Before",[61,84],{"x":79,"y":85,"width":86,"height":87,"rx":88,"fill":89,"stroke":89,"style":90},"88","560","56","5","#0466c8","fill-opacity:0.16",[71,92,96],{"x":93,"y":94,"fill":68,"style":95},"320","113","font-size:12px;font-weight:600;text-anchor:middle","vendor.js 400KB",[71,98,101],{"x":93,"y":99,"fill":68,"style":100},"132","font-size:11px;text-anchor:middle","re-downloaded on bump",[71,103,105],{"x":79,"y":104,"fill":68,"style":81},"184","After",[61,107],{"x":79,"y":108,"width":109,"height":87,"rx":88,"fill":68,"stroke":68,"style":110},"196","150","stroke-opacity:0.45;fill-opacity:0.08",[71,112,115],{"x":113,"y":114,"fill":68,"style":95},"115","220","framework",[71,117,119],{"x":113,"y":118,"fill":68,"style":100},"238","~45KB, stable",[61,121],{"x":122,"y":108,"width":123,"height":87,"rx":88,"fill":124,"stroke":125,"style":126},"200","190","#ffc300","#b8860b","fill-opacity:0.24",[71,128,130],{"x":129,"y":114,"fill":68,"style":95},"295","vendor (slim)",[71,132,133],{"x":129,"y":118,"fill":68,"style":100},"deduped, tree-shaken",[61,135],{"x":136,"y":108,"width":122,"height":87,"rx":88,"fill":89,"stroke":89,"style":137},"400","fill-opacity:0.1;stroke-dasharray:5 4",[71,139,141],{"x":140,"y":114,"fill":68,"style":95},"500","lazy route chunk",[71,143,144],{"x":140,"y":118,"fill":68,"style":100},"chart, loads on route",[146,147],"line",{"x1":73,"y1":148,"x2":149,"y2":148,"stroke":68,"style":150},"268","736","stroke-opacity:0.3",[71,152,155],{"x":73,"y":153,"fill":68,"style":154},"290","font-size:12px","Isolate the runtime so a deploy no longer invalidates the whole vendor.",[157,158,160],"h2",{"id":159},"rapid-diagnosis-confirming-the-vendor-chunk-is-the-problem","Rapid diagnosis: confirming the vendor chunk is the problem",[14,162,163],{},"Before changing config, prove the vendor chunk is the bottleneck and find what is inside it. Run through this DevTools and analyzer checklist:",[165,166,167,179,185,191],"ul",{},[168,169,170,174,175,178],"li",{},[171,172,173],"strong",{},"Network tab (Disable cache, Fast 3G throttle):"," sort by transfer size. If ",[29,176,177],{},"vendor.[hash].js"," is the largest resource and blocks the route, it is your target.",[168,180,181,184],{},[171,182,183],{},"Coverage tab:"," record a page load and read the unused-bytes percentage for the vendor chunk. Above ~40% unused means dead weight is shipping.",[168,186,187,190],{},[171,188,189],{},"Performance panel:"," look for a single Compile\u002FEvaluate Script task over 50ms — that long task is the vendor chunk parsing on the main thread.",[168,192,193,199,200,204],{},[171,194,195,198],{},[29,196,197],{},"webpack-bundle-analyzer",":"," generate the treemap and note the three biggest rectangles. Those three libraries are where 80% of your savings live. (See ",[18,201,203],{"href":202},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fwebpack-bundle-analysis-techniques\u002Fhow-to-configure-webpack-bundle-analyzer-for-production\u002F","how to configure webpack bundle analyzer for production"," if you have not set this up.)",[206,207,212],"pre",{"className":208,"code":209,"language":210,"meta":211,"style":211},"language-bash shiki shiki-themes github-light-high-contrast github-light-high-contrast github-light-high-contrast","# Produce a static treemap from your production stats\nnpx webpack --mode production --json > stats.json\nnpx webpack-bundle-analyzer stats.json dist --mode static --report report.html\n# trade-off: analyze a PRODUCTION build only — dev builds include HMR runtime\n# and unminified deps, so their treemap proportions mislead you.\n","bash","",[29,213,214,222,250,275,281],{"__ignoreMap":211},[215,216,218],"span",{"class":146,"line":217},1,[215,219,221],{"class":220},"sIIH1","# Produce a static treemap from your production stats\n",[215,223,225,229,233,237,240,243,247],{"class":146,"line":224},2,[215,226,228],{"class":227},"seIZK","npx",[215,230,232],{"class":231},"s-_DF"," webpack",[215,234,236],{"class":235},"sf6mN"," --mode",[215,238,239],{"class":231}," production",[215,241,242],{"class":235}," --json",[215,244,246],{"class":245},"sP5qI"," >",[215,248,249],{"class":231}," stats.json\n",[215,251,253,255,258,261,264,266,269,272],{"class":146,"line":252},3,[215,254,228],{"class":227},[215,256,257],{"class":231}," webpack-bundle-analyzer",[215,259,260],{"class":231}," stats.json",[215,262,263],{"class":231}," dist",[215,265,236],{"class":235},[215,267,268],{"class":231}," static",[215,270,271],{"class":235}," --report",[215,273,274],{"class":231}," report.html\n",[215,276,278],{"class":146,"line":277},4,[215,279,280],{"class":220},"# trade-off: analyze a PRODUCTION build only — dev builds include HMR runtime\n",[215,282,284],{"class":146,"line":283},5,[215,285,286],{"class":220},"# and unminified deps, so their treemap proportions mislead you.\n",[157,288,290],{"id":289},"root-cause-analysis-why-vendor-chunks-balloon-in-react-apps","Root cause analysis: why vendor chunks balloon in React apps",[14,292,293,299,300,303,304,307,308,312],{},[171,294,295,296,298],{},"Failure mode 1 — the monolithic ",[29,297,38],{}," catch-all."," A single ",[29,301,302],{},"cacheGroups"," rule with ",[29,305,306],{},"test: \u002Fnode_modules\u002F"," lumps React, your UI kit, your charting library, and a date library into one file. Any version bump to ",[309,310,311],"em",{},"any"," dependency invalidates the entire chunk's hash, so returning users re-download all 400KB even though only one library changed.",[14,314,315,51,318,321,322,325,326,330],{},[171,316,317],{},"Failure mode 2 — a heavy library imported at the namespace level.",[29,319,320],{},"import * as Icons from 'react-icons'"," or ",[29,323,324],{},"import _ from 'lodash'"," pulls the whole package because the import defeats static analysis. The mechanism is the same dead-code barrier covered in ",[18,327,329],{"href":328},"\u002Fjavascript-bundle-optimization-code-splitting\u002Ftree-shaking-and-dead-code-elimination\u002Ffixing-tree-shaking-issues-with-lodash-and-moment\u002F","fixing tree-shaking issues with lodash and moment",".",[14,332,333,336],{},[171,334,335],{},"Failure mode 3 — an always-loaded heavy dependency that only one route needs."," A charting library (often 150KB+) or a rich-text editor lives in the vendor chunk and ships to every user, even those who never open the dashboard route that uses it. It belongs in a route-level dynamic import, not the shared vendor file.",[14,338,339,342],{},[171,340,341],{},"Failure mode 4 — duplicate copies of the same dependency."," Two transitive deps requesting different minor versions of, say, a polyfill produce two copies inside vendor. The treemap shows the same package name twice — pure waste.",[157,344,346],{"id":345},"step-by-step-resolution-numbered-fixes-by-impact","Step-by-step resolution: numbered fixes by impact",[348,349,351],"h3",{"id":350},"_1-split-the-monolith-into-stable-cache-groups","1. Split the monolith into stable cache groups",[14,353,354],{},"Carve the rarely-changing framework core away from volatile app dependencies so a single dependency bump no longer invalidates everything.",[206,356,360],{"className":357,"code":358,"language":359,"meta":211,"style":211},"language-js shiki shiki-themes github-light-high-contrast github-light-high-contrast github-light-high-contrast","\u002F\u002F webpack.config.js\nmodule.exports = {\n  optimization: {\n    runtimeChunk: 'single', \u002F\u002F isolate the webpack runtime so its hash churn\n                            \u002F\u002F doesn't invalidate vendor on every build\n    splitChunks: {\n      chunks: 'all',\n      cacheGroups: {\n        framework: {\n          test: \u002F[\\\\\u002F]node_modules[\\\\\u002F](react|react-dom|scheduler)[\\\\\u002F]\u002F,\n          name: 'framework', priority: 40, enforce: true,\n        },\n        vendor: {\n          test: \u002F[\\\\\u002F]node_modules[\\\\\u002F]\u002F,\n          name: 'vendor', priority: 10,\n        },\n      },\n    },\n  },\n  \u002F\u002F trade-off: more chunks means more HTTP requests; on HTTP\u002F1.1 origins the\n  \u002F\u002F request overhead can outweigh the cache-stability win, so don't over-split.\n};\n","js",[29,361,362,367,384,389,403,408,414,426,432,438,490,512,518,524,549,563,568,574,580,586,592,598],{"__ignoreMap":211},[215,363,364],{"class":146,"line":217},[215,365,366],{"class":220},"\u002F\u002F webpack.config.js\n",[215,368,369,372,375,378,381],{"class":146,"line":224},[215,370,371],{"class":235},"module",[215,373,330],{"class":374},"syybb",[215,376,377],{"class":235},"exports",[215,379,380],{"class":245}," =",[215,382,383],{"class":374}," {\n",[215,385,386],{"class":146,"line":252},[215,387,388],{"class":374},"  optimization: {\n",[215,390,391,394,397,400],{"class":146,"line":277},[215,392,393],{"class":374},"    runtimeChunk: ",[215,395,396],{"class":231},"'single'",[215,398,399],{"class":374},", ",[215,401,402],{"class":220},"\u002F\u002F isolate the webpack runtime so its hash churn\n",[215,404,405],{"class":146,"line":283},[215,406,407],{"class":220},"                            \u002F\u002F doesn't invalidate vendor on every build\n",[215,409,411],{"class":146,"line":410},6,[215,412,413],{"class":374},"    splitChunks: {\n",[215,415,417,420,423],{"class":146,"line":416},7,[215,418,419],{"class":374},"      chunks: ",[215,421,422],{"class":231},"'all'",[215,424,425],{"class":374},",\n",[215,427,429],{"class":146,"line":428},8,[215,430,431],{"class":374},"      cacheGroups: {\n",[215,433,435],{"class":146,"line":434},9,[215,436,437],{"class":374},"        framework: {\n",[215,439,441,444,447,450,454,457,459,461,463,465,468,471,474,476,479,481,483,485,488],{"class":146,"line":440},10,[215,442,443],{"class":374},"          test:",[215,445,446],{"class":231}," \u002F",[215,448,449],{"class":235},"[",[215,451,453],{"class":452},"s6uau","\\\\",[215,455,456],{"class":235},"\u002F]",[215,458,38],{"class":231},[215,460,449],{"class":235},[215,462,453],{"class":452},[215,464,456],{"class":235},[215,466,467],{"class":231},"(react",[215,469,470],{"class":245},"|",[215,472,473],{"class":231},"react-dom",[215,475,470],{"class":245},[215,477,478],{"class":231},"scheduler)",[215,480,449],{"class":235},[215,482,453],{"class":452},[215,484,456],{"class":235},[215,486,487],{"class":231},"\u002F",[215,489,425],{"class":374},[215,491,493,496,499,502,504,507,510],{"class":146,"line":492},11,[215,494,495],{"class":374},"          name: ",[215,497,498],{"class":231},"'framework'",[215,500,501],{"class":374},", priority: ",[215,503,79],{"class":235},[215,505,506],{"class":374},", enforce: ",[215,508,509],{"class":235},"true",[215,511,425],{"class":374},[215,513,515],{"class":146,"line":514},12,[215,516,517],{"class":374},"        },\n",[215,519,521],{"class":146,"line":520},13,[215,522,523],{"class":374},"        vendor: {\n",[215,525,527,529,531,533,535,537,539,541,543,545,547],{"class":146,"line":526},14,[215,528,443],{"class":374},[215,530,446],{"class":231},[215,532,449],{"class":235},[215,534,453],{"class":452},[215,536,456],{"class":235},[215,538,38],{"class":231},[215,540,449],{"class":235},[215,542,453],{"class":452},[215,544,456],{"class":235},[215,546,487],{"class":231},[215,548,425],{"class":374},[215,550,552,554,557,559,561],{"class":146,"line":551},15,[215,553,495],{"class":374},[215,555,556],{"class":231},"'vendor'",[215,558,501],{"class":374},[215,560,66],{"class":235},[215,562,425],{"class":374},[215,564,566],{"class":146,"line":565},16,[215,567,517],{"class":374},[215,569,571],{"class":146,"line":570},17,[215,572,573],{"class":374},"      },\n",[215,575,577],{"class":146,"line":576},18,[215,578,579],{"class":374},"    },\n",[215,581,583],{"class":146,"line":582},19,[215,584,585],{"class":374},"  },\n",[215,587,589],{"class":146,"line":588},20,[215,590,591],{"class":220},"  \u002F\u002F trade-off: more chunks means more HTTP requests; on HTTP\u002F1.1 origins the\n",[215,593,595],{"class":146,"line":594},21,[215,596,597],{"class":220},"  \u002F\u002F request overhead can outweigh the cache-stability win, so don't over-split.\n",[215,599,601],{"class":146,"line":600},22,[215,602,603],{"class":374},"};\n",[14,605,606],{},"Expected outcome: isolates ~45KB of stable framework code into a long-lived chunk; reduces the bytes re-downloaded per deploy by the framework's share (often ~30-40% of repeat-visit transfer).",[348,608,610],{"id":609},"_2-lazy-load-route-only-heavy-dependencies","2. Lazy-load route-only heavy dependencies",[14,612,613,614,617],{},"Move the charting library, editor, or map component out of vendor and behind ",[29,615,616],{},"React.lazy"," so it loads only on the route that uses it.",[206,619,623],{"className":620,"code":621,"language":622,"meta":211,"style":211},"language-jsx shiki shiki-themes github-light-high-contrast github-light-high-contrast github-light-high-contrast","import { lazy, Suspense } from 'react';\n\u002F\u002F Chart (and its ~150KB dep tree) now splits into its own async chunk.\nconst Dashboard = lazy(() => import('.\u002Froutes\u002FDashboard'));\n\nexport function Routes() {\n  return (\n    \u003CSuspense fallback={\u003CSpinner \u002F>}>\n      \u003CDashboard \u002F>\n    \u003C\u002FSuspense>\n  );\n  \u002F\u002F trade-off: lazy boundaries add a loading state and a round-trip on first\n  \u002F\u002F navigation; don't lazy-load components used on the very first paint or you\n  \u002F\u002F trade a smaller vendor chunk for a worse LCP.\n}\n","jsx",[29,624,625,642,647,679,685,699,707,737,748,757,762,767,772,777],{"__ignoreMap":211},[215,626,627,630,633,636,639],{"class":146,"line":217},[215,628,629],{"class":245},"import",[215,631,632],{"class":374}," { lazy, Suspense } ",[215,634,635],{"class":245},"from",[215,637,638],{"class":231}," 'react'",[215,640,641],{"class":374},";\n",[215,643,644],{"class":146,"line":224},[215,645,646],{"class":220},"\u002F\u002F Chart (and its ~150KB dep tree) now splits into its own async chunk.\n",[215,648,649,652,655,657,661,664,667,670,673,676],{"class":146,"line":252},[215,650,651],{"class":245},"const",[215,653,654],{"class":235}," Dashboard",[215,656,380],{"class":245},[215,658,660],{"class":659},"ssM3C"," lazy",[215,662,663],{"class":374},"(() ",[215,665,666],{"class":245},"=>",[215,668,669],{"class":245}," import",[215,671,672],{"class":374},"(",[215,674,675],{"class":231},"'.\u002Froutes\u002FDashboard'",[215,677,678],{"class":374},"));\n",[215,680,681],{"class":146,"line":277},[215,682,684],{"emptyLinePlaceholder":683},true,"\n",[215,686,687,690,693,696],{"class":146,"line":283},[215,688,689],{"class":245},"export",[215,691,692],{"class":245}," function",[215,694,695],{"class":659}," Routes",[215,697,698],{"class":374},"() {\n",[215,700,701,704],{"class":146,"line":410},[215,702,703],{"class":245},"  return",[215,705,706],{"class":374}," (\n",[215,708,709,712,716,719,722,725,728,731,734],{"class":146,"line":416},[215,710,711],{"class":374},"    \u003C",[215,713,715],{"class":714},"s-fAs","Suspense",[215,717,718],{"class":235}," fallback",[215,720,721],{"class":245},"={",[215,723,724],{"class":374},"\u003C",[215,726,727],{"class":714},"Spinner",[215,729,730],{"class":374}," \u002F>",[215,732,733],{"class":245},"}",[215,735,736],{"class":374},">\n",[215,738,739,742,745],{"class":146,"line":428},[215,740,741],{"class":374},"      \u003C",[215,743,744],{"class":714},"Dashboard",[215,746,747],{"class":374}," \u002F>\n",[215,749,750,753,755],{"class":146,"line":434},[215,751,752],{"class":374},"    \u003C\u002F",[215,754,715],{"class":714},[215,756,736],{"class":374},[215,758,759],{"class":146,"line":440},[215,760,761],{"class":374},"  );\n",[215,763,764],{"class":146,"line":492},[215,765,766],{"class":220},"  \u002F\u002F trade-off: lazy boundaries add a loading state and a round-trip on first\n",[215,768,769],{"class":146,"line":514},[215,770,771],{"class":220},"  \u002F\u002F navigation; don't lazy-load components used on the very first paint or you\n",[215,773,774],{"class":146,"line":520},[215,775,776],{"class":220},"  \u002F\u002F trade a smaller vendor chunk for a worse LCP.\n",[215,778,779],{"class":146,"line":526},[215,780,781],{"class":374},"}\n",[14,783,784],{},"Expected outcome: removes the route-specific library from every page's critical path. Moving a 150KB (≈45KB gzipped) charting lib out of vendor cuts the initial vendor transfer by roughly that amount.",[348,786,788],{"id":787},"_3-convert-namespace-imports-to-nameddeep-imports","3. Convert namespace imports to named\u002Fdeep imports",[14,790,791],{},"Replace whole-package imports with named or path imports so tree-shaking can prune the unused surface.",[206,793,795],{"className":620,"code":794,"language":622,"meta":211,"style":211},"\u002F\u002F BEFORE: pulls the entire icon set into vendor\n\u002F\u002F import * as Icons from 'react-icons\u002Ffa';\n\u002F\u002F AFTER: only the icons you use survive tree-shaking\nimport { FaUser, FaCog } from 'react-icons\u002Ffa';\n\u002F\u002F trade-off: deep\u002Fnamed imports rely on the package shipping ESM with\n\u002F\u002F sideEffects:false — if it ships CommonJS, this won't shake; replace the dep.\n",[29,796,797,802,807,812,826,831],{"__ignoreMap":211},[215,798,799],{"class":146,"line":217},[215,800,801],{"class":220},"\u002F\u002F BEFORE: pulls the entire icon set into vendor\n",[215,803,804],{"class":146,"line":224},[215,805,806],{"class":220},"\u002F\u002F import * as Icons from 'react-icons\u002Ffa';\n",[215,808,809],{"class":146,"line":252},[215,810,811],{"class":220},"\u002F\u002F AFTER: only the icons you use survive tree-shaking\n",[215,813,814,816,819,821,824],{"class":146,"line":277},[215,815,629],{"class":245},[215,817,818],{"class":374}," { FaUser, FaCog } ",[215,820,635],{"class":245},[215,822,823],{"class":231}," 'react-icons\u002Ffa'",[215,825,641],{"class":374},[215,827,828],{"class":146,"line":283},[215,829,830],{"class":220},"\u002F\u002F trade-off: deep\u002Fnamed imports rely on the package shipping ESM with\n",[215,832,833],{"class":146,"line":410},[215,834,835],{"class":220},"\u002F\u002F sideEffects:false — if it ships CommonJS, this won't shake; replace the dep.\n",[14,837,838],{},"Expected outcome: for an icon or utility library, typically reduces that library's vendor contribution from tens of KB to single-digit KB gzipped.",[348,840,842],{"id":841},"_4-deduplicate-and-right-size-individual-libraries","4. Deduplicate and right-size individual libraries",[14,844,845],{},"Resolve duplicate copies and swap heavyweight libraries for lean equivalents.",[206,847,849],{"className":357,"code":848,"language":359,"meta":211,"style":211},"\u002F\u002F webpack.config.js — force a single copy of a duplicated dependency\nmodule.exports = {\n  resolve: {\n    alias: {\n      \u002F\u002F collapse two requested ranges to one resolved copy\n      'date-fns': require.resolve('date-fns'),\n    },\n  },\n  \u002F\u002F trade-off: aliasing to one version can break a transitive dep that relied\n  \u002F\u002F on the other range's API — run the test suite after pinning.\n};\n",[29,850,851,856,868,873,878,883,902,906,910,915,920],{"__ignoreMap":211},[215,852,853],{"class":146,"line":217},[215,854,855],{"class":220},"\u002F\u002F webpack.config.js — force a single copy of a duplicated dependency\n",[215,857,858,860,862,864,866],{"class":146,"line":224},[215,859,371],{"class":235},[215,861,330],{"class":374},[215,863,377],{"class":235},[215,865,380],{"class":245},[215,867,383],{"class":374},[215,869,870],{"class":146,"line":252},[215,871,872],{"class":374},"  resolve: {\n",[215,874,875],{"class":146,"line":277},[215,876,877],{"class":374},"    alias: {\n",[215,879,880],{"class":146,"line":283},[215,881,882],{"class":220},"      \u002F\u002F collapse two requested ranges to one resolved copy\n",[215,884,885,888,891,894,896,899],{"class":146,"line":410},[215,886,887],{"class":231},"      'date-fns'",[215,889,890],{"class":374},": require.",[215,892,893],{"class":659},"resolve",[215,895,672],{"class":374},[215,897,898],{"class":231},"'date-fns'",[215,900,901],{"class":374},"),\n",[215,903,904],{"class":146,"line":416},[215,905,579],{"class":374},[215,907,908],{"class":146,"line":428},[215,909,585],{"class":374},[215,911,912],{"class":146,"line":434},[215,913,914],{"class":220},"  \u002F\u002F trade-off: aliasing to one version can break a transitive dep that relied\n",[215,916,917],{"class":146,"line":440},[215,918,919],{"class":220},"  \u002F\u002F on the other range's API — run the test suite after pinning.\n",[215,921,922],{"class":146,"line":492},[215,923,603],{"class":374},[14,925,926,927,930,931,321,934,937],{},"Alongside this, swap ",[29,928,929],{},"moment"," (~18KB gzipped core, more with locales) for ",[29,932,933],{},"date-fns",[29,935,936],{},"dayjs"," (~2KB core). Expected outcome: deduplication removes the redundant copy outright; the moment→dayjs swap saves ~15KB gzipped.",[157,939,941],{"id":940},"verification-proving-the-chunk-shrank-and-stayed-shrunk","Verification: proving the chunk shrank and stayed shrunk",[14,943,944],{},"Re-run the same diagnosis against the new build and capture a before\u002Fafter diff:",[165,946,947,953,963,972],{},[168,948,949,952],{},[171,950,951],{},"Before\u002Fafter analyzer treemap:"," the vendor rectangle should be visibly smaller and the framework chunk should appear separately. Confirm no duplicate package names remain.",[168,954,955,958,959,962],{},[171,956,957],{},"Network transfer:"," with cache disabled, the sum of ",[29,960,961],{},"framework + vendor"," initial JS should land under the 150KB gzipped initial-route budget.",[168,964,965,967,968,330],{},[171,966,189],{}," the previously >50ms vendor Evaluate Script task should now be split across smaller chunks, each under the long-task threshold — the same responsiveness win discussed in ",[18,969,971],{"href":970},"\u002Fcore-web-vitals-measurement\u002Foptimizing-first-input-delay-fid\u002F","optimizing First Input Delay and INP",[168,973,974,977],{},[171,975,976],{},"CI assertion:"," lock the win in so it cannot regress.",[206,979,981],{"className":357,"code":980,"language":359,"meta":211,"style":211},"\u002F\u002F CI budget — fail the build if vendor grows past target (bytes, gzipped)\nmodule.exports = {\n  performance: {\n    hints: 'error',\n    maxAssetSize: 160_000,       \u002F\u002F per-asset cap\n    maxEntrypointSize: 170_000,  \u002F\u002F initial route budget\n  },\n  \u002F\u002F trade-off: hard error budgets can block urgent hotfixes that legitimately\n  \u002F\u002F add bytes; pair the gate with an explicit override label for emergencies.\n};\n",[29,982,983,988,1000,1005,1015,1029,1043,1047,1052,1057],{"__ignoreMap":211},[215,984,985],{"class":146,"line":217},[215,986,987],{"class":220},"\u002F\u002F CI budget — fail the build if vendor grows past target (bytes, gzipped)\n",[215,989,990,992,994,996,998],{"class":146,"line":224},[215,991,371],{"class":235},[215,993,330],{"class":374},[215,995,377],{"class":235},[215,997,380],{"class":245},[215,999,383],{"class":374},[215,1001,1002],{"class":146,"line":252},[215,1003,1004],{"class":374},"  performance: {\n",[215,1006,1007,1010,1013],{"class":146,"line":277},[215,1008,1009],{"class":374},"    hints: ",[215,1011,1012],{"class":231},"'error'",[215,1014,425],{"class":374},[215,1016,1017,1020,1023,1026],{"class":146,"line":283},[215,1018,1019],{"class":374},"    maxAssetSize: ",[215,1021,1022],{"class":235},"160_000",[215,1024,1025],{"class":374},",       ",[215,1027,1028],{"class":220},"\u002F\u002F per-asset cap\n",[215,1030,1031,1034,1037,1040],{"class":146,"line":410},[215,1032,1033],{"class":374},"    maxEntrypointSize: ",[215,1035,1036],{"class":235},"170_000",[215,1038,1039],{"class":374},",  ",[215,1041,1042],{"class":220},"\u002F\u002F initial route budget\n",[215,1044,1045],{"class":146,"line":416},[215,1046,585],{"class":374},[215,1048,1049],{"class":146,"line":428},[215,1050,1051],{"class":220},"  \u002F\u002F trade-off: hard error budgets can block urgent hotfixes that legitimately\n",[215,1053,1054],{"class":146,"line":434},[215,1055,1056],{"class":220},"  \u002F\u002F add bytes; pair the gate with an explicit override label for emergencies.\n",[215,1058,1059],{"class":146,"line":440},[215,1060,603],{"class":374},[14,1062,1063],{},"A field check closes the loop: watch your RUM p75 for the affected routes after the deploy. The repeat-visit transfer should drop (thanks to the stable framework chunk) and the first-interaction metrics should improve as the smaller chunks parse faster.",[157,1065,1067],{"id":1066},"related","Related",[165,1069,1070,1076,1082,1088,1095],{},[168,1071,1072,1075],{},[18,1073,1074],{"href":20},"Webpack bundle analysis techniques"," — the parent guide on measuring what is inside your chunks.",[168,1077,1078,1081],{},[18,1079,1080],{"href":202},"How to configure webpack bundle analyzer for production"," — generating the treemap this workflow depends on.",[168,1083,1084,1087],{},[18,1085,1086],{"href":328},"Fixing tree-shaking issues with lodash and moment"," — eliminating the namespace-import bloat behind failure mode 2.",[168,1089,1090,1094],{},[18,1091,1093],{"href":1092},"\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 chunking control compares across bundlers.",[168,1096,1097,1100],{},[18,1098,1099],{"href":970},"Optimizing First Input Delay and INP"," — why smaller, split chunks reduce parse-driven input delay.",[1102,1103,1105],"script",{"type":1104},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@graph\": [\n    {\n      \"@type\": \"TechArticle\",\n      \"headline\": \"Reducing vendor chunk size in a React app\",\n      \"description\": \"A diagnosis-to-verification workflow for shrinking an oversized webpack vendor chunk in a React application, with root causes, numbered fixes, and CI budgets.\",\n      \"datePublished\": \"2026-06-18\",\n      \"dateModified\": \"2026-06-18\",\n      \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fwebpack-bundle-analysis-techniques\u002Freducing-vendor-chunk-size-in-a-react-app\u002F\"\n    },\n    {\n      \"@type\": \"HowTo\",\n      \"name\": \"Reduce an oversized webpack vendor chunk in a React app\",\n      \"step\": [\n        { \"@type\": \"HowToStep\", \"position\": 1, \"name\": \"Split into stable cache groups\", \"text\": \"Separate the React framework core from volatile app dependencies and isolate the runtime chunk.\" },\n        { \"@type\": \"HowToStep\", \"position\": 2, \"name\": \"Lazy-load route-only heavy deps\", \"text\": \"Move charting, editor, or map libraries behind React.lazy so they load only on their route.\" },\n        { \"@type\": \"HowToStep\", \"position\": 3, \"name\": \"Convert namespace imports\", \"text\": \"Replace whole-package imports with named or deep imports so tree-shaking prunes unused code.\" },\n        { \"@type\": \"HowToStep\", \"position\": 4, \"name\": \"Deduplicate and right-size\", \"text\": \"Collapse duplicate dependency copies and swap heavy libraries for lean equivalents.\" },\n        { \"@type\": \"HowToStep\", \"position\": 5, \"name\": \"Verify and budget\", \"text\": \"Re-run the analyzer, confirm the initial JS budget, and add a CI size assertion to prevent regression.\" }\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\": \"Webpack Bundle Analysis Techniques\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fwebpack-bundle-analysis-techniques\u002F\" },\n        { \"@type\": \"ListItem\", \"position\": 4, \"name\": \"Reducing vendor chunk size in a React app\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fwebpack-bundle-analysis-techniques\u002Freducing-vendor-chunk-size-in-a-react-app\u002F\" }\n      ]\n    }\n  ]\n}\n",[1107,1108,1109],"style",{},"html pre.shiki code .sIIH1, html code.shiki .sIIH1{--shiki-default:#66707B;--shiki-dark:#66707B;--shiki-light:#66707B}html pre.shiki code .seIZK, html code.shiki .seIZK{--shiki-default:#702C00;--shiki-dark:#702C00;--shiki-light:#702C00}html pre.shiki code .s-_DF, html code.shiki .s-_DF{--shiki-default:#032563;--shiki-dark:#032563;--shiki-light:#032563}html pre.shiki code .sf6mN, html code.shiki .sf6mN{--shiki-default:#023B95;--shiki-dark:#023B95;--shiki-light:#023B95}html pre.shiki code .sP5qI, html code.shiki .sP5qI{--shiki-default:#A0111F;--shiki-dark:#A0111F;--shiki-light:#A0111F}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 .syybb, html code.shiki .syybb{--shiki-default:#0E1116;--shiki-dark:#0E1116;--shiki-light:#0E1116}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 pre.shiki code .ssM3C, html code.shiki .ssM3C{--shiki-default:#622CBC;--shiki-dark:#622CBC;--shiki-light:#622CBC}html pre.shiki code .s-fAs, html code.shiki .s-fAs{--shiki-default:#024C1A;--shiki-dark:#024C1A;--shiki-light:#024C1A}",{"title":211,"searchDepth":224,"depth":224,"links":1111},[1112,1113,1114,1120,1121],{"id":159,"depth":224,"text":160},{"id":289,"depth":224,"text":290},{"id":345,"depth":224,"text":346,"children":1115},[1116,1117,1118,1119],{"id":350,"depth":252,"text":351},{"id":609,"depth":252,"text":610},{"id":787,"depth":252,"text":788},{"id":841,"depth":252,"text":842},{"id":940,"depth":224,"text":941},{"id":1066,"depth":224,"text":1067},"A diagnosis-to-verification workflow for shrinking an oversized webpack vendor chunk in a React application.","md",{"slug":12,"type":1125,"breadcrumb":1126,"datePublished":1135,"dateModified":1135},"long_tail",[1127,1129,1131,1133],{"name":1128,"url":487},"Home",{"name":1130,"url":25},"JavaScript Bundle Optimization & Code Splitting",{"name":1132,"url":20},"Webpack Bundle Analysis Techniques",{"name":5,"url":1134},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fwebpack-bundle-analysis-techniques\u002Freducing-vendor-chunk-size-in-a-react-app\u002F","2026-06-18","\u002Fjavascript-bundle-optimization-code-splitting\u002Fwebpack-bundle-analysis-techniques\u002Freducing-vendor-chunk-size-in-a-react-app",{"title":1138,"description":1139},"Reducing Vendor Chunk Size in a React App","Diagnose and shrink an oversized webpack vendor chunk in a React app: root causes, numbered fixes with expected byte savings, and verification against bundle budgets.","javascript-bundle-optimization-code-splitting\u002Fwebpack-bundle-analysis-techniques\u002Freducing-vendor-chunk-size-in-a-react-app\u002Findex","AOEA87pF4xNYBxuE07cFSMgo3OEfEDLiIvpImQhBCAU",[1143,1147],{"title":1144,"path":1145,"stem":1146,"children":-1},"Configure webpack-bundle-analyzer for Production","\u002Fjavascript-bundle-optimization-code-splitting\u002Fwebpack-bundle-analysis-techniques\u002Fhow-to-configure-webpack-bundle-analyzer-for-production","javascript-bundle-optimization-code-splitting\u002Fwebpack-bundle-analysis-techniques\u002Fhow-to-configure-webpack-bundle-analyzer-for-production\u002Findex",null,1782237170920]