[{"data":1,"prerenderedAt":1097},["ShallowReactive",2],{"content:\u002Fjavascript-bundle-optimization-code-splitting\u002Fwebpack-bundle-analysis-techniques\u002Ftuning-splitchunks-cachegroups-for-long-term-caching":3,"surroundings:\u002Fjavascript-bundle-optimization-code-splitting\u002Fwebpack-bundle-analysis-techniques\u002Ftuning-splitchunks-cachegroups-for-long-term-caching":1089},{"id":4,"title":5,"body":6,"description":1069,"extension":1070,"meta":1071,"navigation":1082,"path":1083,"seo":1084,"stem":1087,"__hash__":1088},"content\u002Fjavascript-bundle-optimization-code-splitting\u002Fwebpack-bundle-analysis-techniques\u002Ftuning-splitchunks-cachegroups-for-long-term-caching\u002Findex.md","Tuning splitChunks cacheGroups for Long-Term Caching",{"type":7,"value":8,"toc":1053},"minimark",[9,14,34,41,135,140,196,200,209,215,221,234,309,313,318,431,434,438,708,711,715,721,725,731,823,827,830,834,854,858,895,899,909,918,927,935,939,951,964,984,993,1002,1011,1015,1038,1043,1046,1049],[10,11,13],"h1",{"id":12},"how-to-tune-webpack-splitchunks-cachegroups-for-long-term-caching","How to Tune webpack splitChunks cacheGroups for Long-Term Caching",[15,16,17,18,23,24,28,29,33],"p",{},"This guide is part of ",[19,20,22],"a",{"href":21},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fwebpack-bundle-analysis-techniques\u002F","Webpack Bundle Analysis Techniques"," within ",[19,25,27],{"href":26},"\u002Fjavascript-bundle-optimization-code-splitting\u002F","JavaScript Bundle Optimization & Code Splitting",". Returning visitors are a large share of most sites' traffic, and for them the fastest JavaScript is the JavaScript already in the browser cache. Long-term caching works only if file names stay the same when contents do not change — which means chunk contents must be stable across deploys. webpack's ",[30,31,32],"code",{},"optimization.splitChunks"," decides those contents.",[15,35,36,37,40],{},"The defaults split shared modules into chunks based on size and request-count heuristics. They are reasonable for first-load size and poor for cache stability: a vendor chunk that mixes React with a frequently updated internal package changes hash on every release, and every returning user downloads it again. Tuning ",[30,38,39],{},"cacheGroups"," around update frequency, combined with deterministic module and chunk IDs, can raise the share of unchanged bytes per deploy from under half to well over 80%.",[15,42,43],{},[44,45,51,52,51,59,51,63,51,66,51,75,51,81,51,89,51,95,51,101,51,104,51,108,51,111,51,115,51,119,51,122,51,128,51,132,51],"svg",{"viewBox":46,"width":47,"role":48,"ariaLabel":49,"style":50},"0 0 760 268","100%","img","A recommended splitChunks cache group layout from the most stable framework chunk to the per-deploy application code.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[53,54],"rect",{"className":55,"x":57,"y":57,"width":47,"height":47,"fill":58},[56],"svg-canvas","0","#ffffff",[60,61,62],"title",{},"Cache groups ordered by stability",[64,65,49],"desc",{},[53,67],{"x":68,"y":68,"width":69,"height":70,"rx":71,"fill":72,"stroke":73,"style":74},"1","758","266","10","none","currentColor","stroke-opacity:0.18",[76,77,62],"text",{"x":78,"y":79,"fill":73,"style":80},"28.0","34.0","font-size:16px;font-weight:700",[53,82],{"x":78,"y":83,"width":84,"height":85,"rx":86,"fill":87,"stroke":87,"style":88},"56.0","704.0","40.0","6","#0466c8","fill-opacity:0.14;stroke-opacity:0.9",[76,90,94],{"x":91,"y":92,"fill":73,"style":93},"42.0","80.5","font-size:13px;font-weight:700","runtime",[76,96,100],{"x":97,"y":92,"fill":73,"style":98,"textAnchor":99},"131.4","font-size:12px","start","webpack runtime — tiny, changes when the chunk graph changes; inline or separate",[53,102],{"x":78,"y":103,"width":84,"height":85,"rx":86,"fill":87,"stroke":87,"style":88},"106.0",[76,105,107],{"x":91,"y":106,"fill":73,"style":93},"130.5","framework",[76,109,110],{"x":97,"y":106,"fill":73,"style":98,"textAnchor":99},"react, react-dom, scheduler — changes on framework upgrades only",[53,112],{"x":78,"y":113,"width":84,"height":85,"rx":86,"fill":73,"stroke":73,"style":114},"156.0","fill-opacity:0.06;stroke-opacity:0.4",[76,116,118],{"x":91,"y":117,"fill":73,"style":93},"180.5","vendors",[76,120,121],{"x":97,"y":117,"fill":73,"style":98,"textAnchor":99},"other node_modules used broadly — changes on dependency updates",[53,123],{"x":78,"y":124,"width":84,"height":85,"rx":86,"fill":125,"stroke":126,"style":127},"206.0","#ffc300","#b8860b","fill-opacity:0.24;stroke-opacity:0.9",[76,129,131],{"x":91,"y":130,"fill":73,"style":93},"230.5","app chunks",[76,133,134],{"x":97,"y":130,"fill":73,"style":98,"textAnchor":99},"your code, split by route — changes every deploy",[136,137,139],"h2",{"id":138},"rapid-diagnosis","Rapid Diagnosis",[141,142,143,151,172,188],"ul",{},[144,145,146,150],"li",{},[147,148,149],"strong",{},"Diff emitted filenames between two consecutive production builds"," that changed only application code. Count how many vendor chunks changed hash.",[144,152,153,159,160,163,164,167,168,171],{},[147,154,155,156],{},"Check ",[30,157,158],{},"output.filename"," uses ",[30,161,162],{},"[contenthash]",", not ",[30,165,166],{},"[hash]"," or ",[30,169,170],{},"[chunkhash]",".",[144,173,174,183,184,187],{},[147,175,155,176,179,180,171],{},[30,177,178],{},"optimization.moduleIds"," and ",[30,181,182],{},"chunkIds"," They should be ",[30,185,186],{},"'deterministic'"," (the production default in webpack 5); numeric IDs shift when modules are added.",[144,189,190,195],{},[147,191,155,192,171],{},[30,193,194],{},"runtimeChunk"," Without a separate runtime, the runtime's chunk manifest is embedded in the entry and changes its hash whenever any chunk changes.",[136,197,199],{"id":198},"root-cause-analysis","Root Cause Analysis",[15,201,202,205,206,208],{},[147,203,204],{},"1. Mixed-stability vendor chunks."," One ",[30,207,118],{}," group containing both stable libraries and frequently updated packages changes whenever any of them do.",[15,210,211,214],{},[147,212,213],{},"2. Unstable IDs."," Module IDs based on order shift when unrelated modules are added, changing contents (and hashes) of chunks that did not really change.",[15,216,217,220],{},[147,218,219],{},"3. Embedded runtime manifest."," The runtime holds the map from chunk IDs to filenames; if it lives in the entry chunk, the entry's hash changes every deploy.",[15,222,223,51,226,229,230,233],{},[147,224,225],{},"4. Over-splitting.",[30,227,228],{},"maxSize"," and aggressive ",[30,231,232],{},"minSize"," settings fragment code into many chunks whose boundaries shift between builds.",[15,235,236],{},[44,237,51,240,51,243,51,246,51,248,51,251,51,253,51,260,51,267,51,272,51,276,51,281,51,285,51,289,51,293,51,297,51,301,51,305,51],{"viewBox":238,"width":47,"role":48,"ariaLabel":239,"style":50},"0 0 760 194","Bar chart of the percentage of JavaScript bytes with unchanged hashes after a deploy that changed only application code, under four configurations.",[53,241],{"className":242,"x":57,"y":57,"width":47,"height":47,"fill":58},[56],[60,244,245],{},"Share of JS bytes unchanged after an app-only deploy",[64,247,239],{},[53,249],{"x":68,"y":68,"width":69,"height":250,"rx":71,"fill":72,"stroke":73,"style":74},"192",[76,252,245],{"x":78,"y":79,"fill":73,"style":80},[76,254,259],{"x":255,"y":256,"fill":73,"style":257,"textAnchor":258},"220.4","70.0","font-size:13px","end","Defaults, runtime in entry",[53,261],{"x":262,"y":83,"width":263,"height":264,"rx":265,"fill":125,"stroke":126,"style":266},"232.4","188.1","19","3","fill-opacity:0.7;stroke-opacity:0.9",[76,268,271],{"x":269,"y":256,"fill":73,"style":270},"426.5","font-size:12px;font-weight:600","38%",[76,273,275],{"x":255,"y":274,"fill":73,"style":257,"textAnchor":258},"101.0","+ runtimeChunk single",[53,277],{"x":262,"y":278,"width":279,"height":264,"rx":265,"fill":87,"stroke":87,"style":280},"87.0","257.4","fill-opacity:0.55;stroke-opacity:0.9",[76,282,284],{"x":283,"y":274,"fill":73,"style":270},"495.8","52%",[76,286,288],{"x":255,"y":287,"fill":73,"style":257,"textAnchor":258},"132.0","+ framework cacheGroup",[53,290],{"x":262,"y":291,"width":292,"height":264,"rx":265,"fill":87,"stroke":87,"style":280},"118.0","351.5",[76,294,296],{"x":295,"y":287,"fill":73,"style":270},"589.8","71%",[76,298,300],{"x":255,"y":299,"fill":73,"style":257,"textAnchor":258},"163.0","+ vendors split from internal pkgs",[53,302],{"x":262,"y":303,"width":304,"height":264,"rx":265,"fill":87,"stroke":87,"style":280},"149.0","435.6",[76,306,308],{"x":307,"y":299,"fill":73,"style":270},"674.0","88%",[136,310,312],{"id":311},"step-by-step-resolution","Step-by-Step Resolution",[314,315,317],"h3",{"id":316},"_1-use-content-hashes-and-deterministic-ids","1. Use content hashes and deterministic IDs",[319,320,325],"pre",{"className":321,"code":322,"language":323,"meta":324,"style":324},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u002F\u002F webpack.config.js\nmodule.exports = {\n  mode: 'production',\n  output: { filename: '[name].[contenthash].js', chunkFilename: '[name].[contenthash].js' },\n  optimization: { moduleIds: 'deterministic', chunkIds: 'deterministic', runtimeChunk: 'single' },\n};\n\u002F\u002F trade-off: a separate runtime chunk is one more request (a few KB). Many\n\u002F\u002F setups inline it into the HTML to save the request — fine, as long as the\n\u002F\u002F HTML is not long-cached.\n","javascript","",[30,326,327,336,356,369,386,407,413,419,425],{"__ignoreMap":324},[328,329,332],"span",{"class":330,"line":331},"line",1,[328,333,335],{"class":334},"sjfSM","\u002F\u002F webpack.config.js\n",[328,337,339,343,346,349,353],{"class":330,"line":338},2,[328,340,342],{"class":341},"sPXB4","module",[328,344,171],{"class":345},"saISM",[328,347,348],{"class":341},"exports",[328,350,352],{"class":351},"sPARh"," =",[328,354,355],{"class":345}," {\n",[328,357,359,362,366],{"class":330,"line":358},3,[328,360,361],{"class":345},"  mode: ",[328,363,365],{"class":364},"sZ8jY","'production'",[328,367,368],{"class":345},",\n",[328,370,372,375,378,381,383],{"class":330,"line":371},4,[328,373,374],{"class":345},"  output: { filename: ",[328,376,377],{"class":364},"'[name].[contenthash].js'",[328,379,380],{"class":345},", chunkFilename: ",[328,382,377],{"class":364},[328,384,385],{"class":345}," },\n",[328,387,389,392,394,397,399,402,405],{"class":330,"line":388},5,[328,390,391],{"class":345},"  optimization: { moduleIds: ",[328,393,186],{"class":364},[328,395,396],{"class":345},", chunkIds: ",[328,398,186],{"class":364},[328,400,401],{"class":345},", runtimeChunk: ",[328,403,404],{"class":364},"'single'",[328,406,385],{"class":345},[328,408,410],{"class":330,"line":409},6,[328,411,412],{"class":345},"};\n",[328,414,416],{"class":330,"line":415},7,[328,417,418],{"class":334},"\u002F\u002F trade-off: a separate runtime chunk is one more request (a few KB). Many\n",[328,420,422],{"class":330,"line":421},8,[328,423,424],{"class":334},"\u002F\u002F setups inline it into the HTML to save the request — fine, as long as the\n",[328,426,428],{"class":330,"line":427},9,[328,429,430],{"class":334},"\u002F\u002F HTML is not long-cached.\n",[15,432,433],{},"Expected outcome: chunk hashes change only when chunk contents change.",[314,435,437],{"id":436},"_2-define-cache-groups-by-update-frequency","2. Define cache groups by update frequency",[319,439,441],{"className":321,"code":440,"language":323,"meta":324,"style":324},"optimization: {\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      vendors: {\n        test: \u002F[\\\\\u002F]node_modules[\\\\\u002F](?!@acme[\\\\\u002F])\u002F,          \u002F\u002F exclude internal packages\n        name: 'vendors', priority: 20, minChunks: 2, reuseExistingChunk: true,\n      },\n    },\n  },\n},\n\u002F\u002F trade-off: a single 'vendors' chunk for all shared third-party code is\n\u002F\u002F cache-friendly but may include libraries only some routes use. Keep heavy,\n\u002F\u002F route-specific libraries out with a negative lookahead or a separate group.\n",[30,442,443,452,459,472,479,486,541,574,579,586,627,667,672,678,684,690,696,702],{"__ignoreMap":324},[328,444,445,449],{"class":330,"line":331},[328,446,448],{"class":447},"sQw3B","optimization",[328,450,451],{"class":345},": {\n",[328,453,454,457],{"class":330,"line":338},[328,455,456],{"class":447},"  splitChunks",[328,458,451],{"class":345},[328,460,461,464,467,470],{"class":330,"line":358},[328,462,463],{"class":447},"    chunks",[328,465,466],{"class":345},": ",[328,468,469],{"class":364},"'all'",[328,471,368],{"class":345},[328,473,474,477],{"class":330,"line":371},[328,475,476],{"class":447},"    cacheGroups",[328,478,451],{"class":345},[328,480,481,484],{"class":330,"line":388},[328,482,483],{"class":447},"      framework",[328,485,451],{"class":345},[328,487,488,491,494,497,500,504,507,510,512,514,516,519,522,525,527,530,532,534,536,539],{"class":330,"line":409},[328,489,490],{"class":447},"        test",[328,492,493],{"class":345},":",[328,495,496],{"class":364}," \u002F",[328,498,499],{"class":341},"[",[328,501,503],{"class":502},"sn2CQ","\\\\",[328,505,506],{"class":341},"\u002F]",[328,508,509],{"class":364},"node_modules",[328,511,499],{"class":341},[328,513,503],{"class":502},[328,515,506],{"class":341},[328,517,518],{"class":364},"(react",[328,520,521],{"class":351},"|",[328,523,524],{"class":364},"react-dom",[328,526,521],{"class":351},[328,528,529],{"class":364},"scheduler)",[328,531,499],{"class":341},[328,533,503],{"class":502},[328,535,506],{"class":341},[328,537,538],{"class":364},"\u002F",[328,540,368],{"class":345},[328,542,543,546,548,551,554,557,559,562,564,567,569,572],{"class":330,"line":415},[328,544,545],{"class":447},"        name",[328,547,466],{"class":345},[328,549,550],{"class":364},"'framework'",[328,552,553],{"class":345},", ",[328,555,556],{"class":447},"priority",[328,558,466],{"class":345},[328,560,561],{"class":341},"40",[328,563,553],{"class":345},[328,565,566],{"class":447},"enforce",[328,568,466],{"class":345},[328,570,571],{"class":341},"true",[328,573,368],{"class":345},[328,575,576],{"class":330,"line":421},[328,577,578],{"class":345},"      },\n",[328,580,581,584],{"class":330,"line":427},[328,582,583],{"class":447},"      vendors",[328,585,451],{"class":345},[328,587,589,591,593,595,597,599,601,603,605,607,609,612,614,616,618,621,624],{"class":330,"line":588},10,[328,590,490],{"class":447},[328,592,493],{"class":345},[328,594,496],{"class":364},[328,596,499],{"class":341},[328,598,503],{"class":502},[328,600,506],{"class":341},[328,602,509],{"class":364},[328,604,499],{"class":341},[328,606,503],{"class":502},[328,608,506],{"class":341},[328,610,611],{"class":364},"(?!@acme",[328,613,499],{"class":341},[328,615,503],{"class":502},[328,617,506],{"class":341},[328,619,620],{"class":364},")\u002F",[328,622,623],{"class":345},",          ",[328,625,626],{"class":334},"\u002F\u002F exclude internal packages\n",[328,628,630,632,634,637,639,641,643,646,648,651,653,656,658,661,663,665],{"class":330,"line":629},11,[328,631,545],{"class":447},[328,633,466],{"class":345},[328,635,636],{"class":364},"'vendors'",[328,638,553],{"class":345},[328,640,556],{"class":447},[328,642,466],{"class":345},[328,644,645],{"class":341},"20",[328,647,553],{"class":345},[328,649,650],{"class":447},"minChunks",[328,652,466],{"class":345},[328,654,655],{"class":341},"2",[328,657,553],{"class":345},[328,659,660],{"class":447},"reuseExistingChunk",[328,662,466],{"class":345},[328,664,571],{"class":341},[328,666,368],{"class":345},[328,668,670],{"class":330,"line":669},12,[328,671,578],{"class":345},[328,673,675],{"class":330,"line":674},13,[328,676,677],{"class":345},"    },\n",[328,679,681],{"class":330,"line":680},14,[328,682,683],{"class":345},"  },\n",[328,685,687],{"class":330,"line":686},15,[328,688,689],{"class":345},"},\n",[328,691,693],{"class":330,"line":692},16,[328,694,695],{"class":334},"\u002F\u002F trade-off: a single 'vendors' chunk for all shared third-party code is\n",[328,697,699],{"class":330,"line":698},17,[328,700,701],{"class":334},"\u002F\u002F cache-friendly but may include libraries only some routes use. Keep heavy,\n",[328,703,705],{"class":330,"line":704},18,[328,706,707],{"class":334},"\u002F\u002F route-specific libraries out with a negative lookahead or a separate group.\n",[15,709,710],{},"Expected outcome: framework and vendor chunks keep their hashes across application-only deploys.",[314,712,714],{"id":713},"_3-keep-internal-packages-with-application-code","3. Keep internal packages with application code",[15,716,717,718,720],{},"Internal workspace packages change at application cadence; excluding them from ",[30,719,118],{}," (as above) stops them busting the vendor cache.",[314,722,724],{"id":723},"_4-avoid-over-fragmentation","4. Avoid over-fragmentation",[15,726,727,728,730],{},"Leave ",[30,729,228],{}," unset unless you have a specific reason; it splits chunks at arbitrary points, and those points move between builds. If you need smaller chunks for parallel download, prefer explicit groups.",[15,732,733],{},[44,734,51,737,51,740,51,743,51,745,51,748,51,750,51,755,51,760,51,767,51,771,51,775,51,780,51,786,51,791,51,795,51,799,51,803,51,807,51,811,51,815,51,819,51],{"viewBox":735,"width":47,"role":48,"ariaLabel":736,"style":50},"0 0 760 178","Two timelines comparing JavaScript re-downloaded by a returning visitor after a deploy with default chunking and with stability-based cache groups.",[53,738],{"className":739,"x":57,"y":57,"width":47,"height":47,"fill":58},[56],[60,741,742],{},"Returning visitor JS download after a deploy",[64,744,736],{},[53,746],{"x":68,"y":68,"width":69,"height":747,"rx":71,"fill":72,"stroke":73,"style":74},"176",[76,749,742],{"x":78,"y":79,"fill":73,"style":80},[76,751,754],{"x":78,"y":752,"fill":73,"style":753},"74.0","font-size:12.5px;font-weight:700","Defaults",[53,756],{"x":757,"y":83,"width":758,"height":759,"rx":265,"fill":125,"stroke":126,"style":127},"92.3","490.2","26.0",[76,761,766],{"x":762,"y":763,"fill":73,"style":764,"textAnchor":765},"337.4","73.5","font-size:11.5px","middle","re-download 240KB (vendors + app)",[76,768,770],{"x":78,"y":769,"fill":73,"style":753},"112.0","Tuned",[53,772],{"x":757,"y":773,"width":774,"height":759,"rx":265,"fill":87,"stroke":87,"style":88},"94.0","117.5",[76,776,779],{"x":777,"y":778,"fill":73,"style":764,"textAnchor":765},"151.0","111.5","app chunk 38KB",[330,781],{"x1":782,"y1":783,"x2":784,"y2":783,"stroke":73,"style":785},"91.5","134.0","726.0","stroke-opacity:0.4",[76,787,790],{"x":788,"y":777,"fill":789,"style":764,"textAnchor":765},"102.2","#51617a","0ms",[76,792,794],{"x":793,"y":777,"fill":789,"style":764,"textAnchor":765},"170.8","100ms",[76,796,798],{"x":797,"y":777,"fill":789,"style":764,"textAnchor":765},"250.1","200ms",[76,800,802],{"x":801,"y":777,"fill":789,"style":764,"textAnchor":765},"329.4","300ms",[76,804,806],{"x":805,"y":777,"fill":789,"style":764,"textAnchor":765},"408.8","400ms",[76,808,810],{"x":809,"y":777,"fill":789,"style":764,"textAnchor":765},"488.1","500ms",[76,812,814],{"x":813,"y":777,"fill":789,"style":764,"textAnchor":765},"567.4","600ms",[76,816,818],{"x":817,"y":777,"fill":789,"style":764,"textAnchor":765},"646.7","700ms",[76,820,822],{"x":821,"y":777,"fill":789,"style":764,"textAnchor":765},"715.3","800ms",[136,824,826],{"id":825},"verification","Verification",[15,828,829],{},"Build twice: once on the current commit and once after a change that touches only application code. Compare the emitted filenames: framework and vendor files should be identical. In production, monitor returning-visitor LCP and the CDN's ratio of 304\u002Fcached responses for JS after deploys; both should improve. Keep the build comparison as a CI check if hash churn has bitten you before.",[136,831,833],{"id":832},"worked-example-a-weekly-release-train","Worked Example: A Weekly Release Train",[15,835,836,837,839,840,843,844,846,847,850,851,853],{},"A SaaS frontend released weekly and found that returning users' LCP worsened by about 300ms on mobile for two days after each release. Analysis showed 62% of JavaScript bytes changed hash each week, mostly because a single ",[30,838,118],{}," chunk included the company's design-system package, updated in nearly every release, and the runtime manifest lived in the entry chunk. Adding ",[30,841,842],{},"runtimeChunk: 'single'",", a ",[30,845,107],{}," group, and excluding ",[30,848,849],{},"@acme\u002F*"," from ",[30,852,118],{}," raised unchanged bytes to 86% per release. The post-release LCP dip shrank to under 60ms and disappeared within hours instead of days.",[136,855,857],{"id":856},"common-mistakes","Common Mistakes",[141,859,860,871,877,889],{},[144,861,862,870],{},[147,863,864,865,867,868,171],{},"Using ",[30,866,166],{}," instead of ",[30,869,162],{}," Build-level hashes change every build regardless of content.",[144,872,873,876],{},[147,874,875],{},"Naming every vendor package as its own chunk."," It maximises stability per package but multiplies requests and runtime overhead.",[144,878,879,51,882,885,886,888],{},[147,880,881],{},"Forgetting CSS.",[30,883,884],{},"mini-css-extract-plugin"," needs ",[30,887,162],{}," in its filenames too.",[144,890,891,894],{},[147,892,893],{},"Long-caching HTML."," If the HTML referencing the chunks is cached for long, users keep loading old chunk names after a deploy; cache HTML briefly.",[136,896,898],{"id":897},"edge-cases-that-break-hash-stability","Edge Cases That Break Hash Stability",[15,900,901,904,905,908],{},[147,902,903],{},"Non-deterministic plugins."," Plugins that embed build timestamps, git hashes or random values into modules change chunk contents on every build. Move such values into the HTML or a separate tiny ",[30,906,907],{},"version.json"," instead of a shared chunk.",[15,910,911,51,914,917],{},[147,912,913],{},"Environment variables in shared code.",[30,915,916],{},"process.env.BUILD_ID"," referenced in a utility module bakes the value into whichever chunk holds that module. Keep build metadata out of shared chunks.",[15,919,920,923,924,171],{},[147,921,922],{},"Async chunk naming."," Unnamed async chunks receive deterministic IDs in webpack 5, but renaming or reordering dynamic imports can still change which modules group together. Give important async chunks explicit names with ",[30,925,926],{},"webpackChunkName",[15,928,929,51,932,934],{},[147,930,931],{},"CSS ordering differences.",[30,933,884],{}," can emit \"conflicting order\" warnings and produce different CSS chunk contents depending on import order across routes. Resolve the warnings; otherwise CSS hashes churn unpredictably.",[136,936,938],{"id":937},"faq","FAQ",[940,941,944,948],"details",{"className":942},[943],"faq-item",[945,946,947],"summary",{},"Does HTTP\u002F2 make many small chunks free?",[15,949,950],{},"It makes extra requests cheaper, not free. Each chunk still has request overhead, its own compression context (worse ratios for small files) and per-chunk runtime bookkeeping. A handful of stable shared chunks plus route chunks is usually the sweet spot.",[940,952,954,957],{"className":953},[943],[945,955,956],{},"How does this map to Next.js?",[15,958,959,960,963],{},"Next.js configures ",[30,961,962],{},"splitChunks"," itself, with framework and shared-library groups along similar lines. You rarely need to override it; focus instead on keeping heavy libraries out of shared code and on stable dependency versions.",[940,965,967,970],{"className":966},[943],[945,968,969],{},"What is the Vite equivalent?",[15,971,972,975,976,979,980,171],{},[30,973,974],{},"build.rollupOptions.output.manualChunks"," (or ",[30,977,978],{},"advancedChunks"," with Rolldown) plays the same role; see ",[19,981,983],{"href":982},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fvite-and-rollup-build-optimization\u002Fconfiguring-manualchunks-in-vite\u002F","configuring manualChunks in Vite",[940,985,987,990],{"className":986},[943],[945,988,989],{},"How do I measure cache reuse in production?",[15,991,992],{},"Compare chunk filenames referenced by consecutive releases' HTML, weighted by size, to get the share of bytes a returning visitor can reuse. In the field, track the share of JavaScript requests served from the browser cache (transferSize of zero in Resource Timing) for returning sessions before and after a deploy.",[940,994,996,999],{"className":995},[943],[945,997,998],{},"Should the runtime chunk be inlined?",[15,1000,1001],{},"Inlining saves a request and is common. The trade-off is that the HTML changes whenever the chunk graph changes — acceptable because HTML is short-cached anyway. Keep it separate only if your HTML is cached aggressively at the edge.",[940,1003,1005,1008],{"className":1004},[943],[945,1006,1007],{},"Does long-term caching help first-time visitors?",[15,1009,1010],{},"No — first-time visitors download everything regardless of chunking. Cache-oriented chunking optimises the returning-visitor experience, which for many products is the majority of page views. Balance it against first-load size: never move heavy route-specific code into a shared chunk just because it is stable.",[136,1012,1014],{"id":1013},"related","Related",[141,1016,1017,1024,1031],{},[144,1018,1019,1023],{},[19,1020,1022],{"href":1021},"\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"," — keeping the stable chunks small.",[144,1025,1026,1030],{},[19,1027,1029],{"href":1028},"\u002Fadvanced-caching-strategies-cdn-architecture\u002Fhttp-cache-control-headers-explained\u002Fsetting-up-immutable-cache-headers-for-hashed-assets\u002F","Setting up immutable cache headers for hashed assets"," — the HTTP side of long-term caching.",[144,1032,1033,1037],{},[19,1034,1036],{"href":1035},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fwebpack-bundle-analysis-techniques\u002Fcomparing-two-builds-with-statoscope\u002F","Comparing two builds with Statoscope"," — verifying hash stability between builds.",[1039,1040,1042],"script",{"type":1041},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"How to Tune webpack splitChunks cacheGroups for Long-Term Caching\",\n  \"description\": \"How to group modules with splitChunks so chunk hashes stay stable across deploys, maximising browser cache reuse without over-fragmenting.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Use content hashes and deterministic IDs\",\n      \"text\": \"Expected outcome: chunk hashes change only when chunk contents change.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Define cache groups by update frequency\",\n      \"text\": \"Expected outcome: framework and vendor chunks keep their hashes across application-only deploys.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Keep internal packages with application code\",\n      \"text\": \"Internal workspace packages change at application cadence; excluding them from vendors (as above) stops them busting the vendor cache.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Avoid over-fragmentation\",\n      \"text\": \"Leave maxSize unset unless you have a specific reason; it splits chunks at arbitrary points, and those points move between builds.\"\n    }\n  ]\n}\n",[1039,1044,1045],{"type":1041},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"How to Tune webpack splitChunks cacheGroups for Long-Term Caching\",\n  \"description\": \"How to group modules with splitChunks so chunk hashes stay stable across deploys, maximising browser cache reuse without over-fragmenting.\",\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\u002Fwebpack-bundle-analysis-techniques\u002Ftuning-splitchunks-cachegroups-for-long-term-caching\u002F\"\n  }\n}\n",[1039,1047,1048],{"type":1041},"\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\": \"Webpack Bundle Analysis Techniques\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fwebpack-bundle-analysis-techniques\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 4,\n      \"name\": \"Tuning splitChunks cacheGroups for Long-Term Caching\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fwebpack-bundle-analysis-techniques\u002Ftuning-splitchunks-cachegroups-for-long-term-caching\u002F\"\n    }\n  ]\n}\n",[1050,1051,1052],"style",{},"html pre.shiki code .sjfSM, html code.shiki .sjfSM{--shiki-default:#66707B;--shiki-dark:#BDC4CC;--shiki-light:#66707B}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 .sPARh, html code.shiki .sPARh{--shiki-default:#A0111F;--shiki-dark:#FF9492;--shiki-light:#A0111F}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}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 .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}html pre.shiki code .sn2CQ, html code.shiki .sn2CQ{--shiki-default:#024C1A;--shiki-default-font-weight:bold;--shiki-dark:#72F088;--shiki-dark-font-weight:bold;--shiki-light:#024C1A;--shiki-light-font-weight:bold}",{"title":324,"searchDepth":338,"depth":338,"links":1054},[1055,1056,1057,1063,1064,1065,1066,1067,1068],{"id":138,"depth":338,"text":139},{"id":198,"depth":338,"text":199},{"id":311,"depth":338,"text":312,"children":1058},[1059,1060,1061,1062],{"id":316,"depth":358,"text":317},{"id":436,"depth":358,"text":437},{"id":713,"depth":358,"text":714},{"id":723,"depth":358,"text":724},{"id":825,"depth":338,"text":826},{"id":832,"depth":338,"text":833},{"id":856,"depth":338,"text":857},{"id":897,"depth":338,"text":898},{"id":937,"depth":338,"text":938},{"id":1013,"depth":338,"text":1014},"How to group modules with splitChunks so chunk hashes stay stable across deploys, maximising browser cache reuse without over-fragmenting.","md",{"slug":1072,"type":1073,"breadcrumb":1074,"datePublished":1081,"dateModified":1081},"tuning-splitchunks-cachegroups-for-long-term-caching","article",[1075,1077,1078,1079],{"name":1076,"url":538},"Home",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":1080},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fwebpack-bundle-analysis-techniques\u002Ftuning-splitchunks-cachegroups-for-long-term-caching\u002F","2026-10-06",true,"\u002Fjavascript-bundle-optimization-code-splitting\u002Fwebpack-bundle-analysis-techniques\u002Ftuning-splitchunks-cachegroups-for-long-term-caching",{"title":1085,"description":1086},"Tuning webpack splitChunks cacheGroups for Caching","Configure webpack 5 splitChunks cacheGroups, contenthash and moduleIds so vendor chunks stay cached across deploys and returning visitors re-download less.","javascript-bundle-optimization-code-splitting\u002Fwebpack-bundle-analysis-techniques\u002Ftuning-splitchunks-cachegroups-for-long-term-caching\u002Findex","XeWJJngl26FUuvGh37b9H3gebObBE74Nez-_dWTTnfY",[1090,1093],{"title":1022,"path":1091,"stem":1092},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fwebpack-bundle-analysis-techniques\u002Freducing-vendor-chunk-size-in-a-react-app","javascript-bundle-optimization-code-splitting\u002Fwebpack-bundle-analysis-techniques\u002Freducing-vendor-chunk-size-in-a-react-app\u002Findex",{"title":1094,"path":1095,"stem":1096},"Network & Server Response Optimization","\u002Fnetwork-protocol-optimization","network-protocol-optimization\u002Findex",1791308076144]