[{"data":1,"prerenderedAt":1112},["ShallowReactive",2],{"content:\u002Fjavascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002Fcode-splitting-vue-router-routes":3,"surroundings:\u002Fjavascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002Fcode-splitting-vue-router-routes":1104},{"id":4,"title":5,"body":6,"description":1085,"extension":1086,"meta":1087,"navigation":345,"path":1098,"seo":1099,"stem":1102,"__hash__":1103},"content\u002Fjavascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002Fcode-splitting-vue-router-routes\u002Findex.md","Code-Splitting Vue Router Routes",{"type":7,"value":8,"toc":1069},"minimark",[9,14,34,41,135,140,176,180,186,192,198,204,273,277,282,512,515,519,526,623,626,630,633,705,713,717,728,847,851,854,858,861,865,894,898,913,981,985,1005,1014,1027,1031,1054,1059,1062,1065],[10,11,13],"h1",{"id":12},"how-to-code-split-vue-router-routes","How to Code-Split Vue Router Routes",[15,16,17,18,23,24,28,29,33],"p",{},"This guide applies ",[19,20,22],"a",{"href":21},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002F","Dynamic Imports and Route-Based Splitting"," to Vue applications, within ",[19,25,27],{"href":26},"\u002Fjavascript-bundle-optimization-code-splitting\u002F","JavaScript Bundle Optimization & Code Splitting",". A Vue single-page application that imports every page component statically ships the whole app on the first visit: the settings screens, the admin tools, the checkout flow and the marketing pages all parse before the first route renders. On mid-tier phones that inflates Total Blocking Time and pushes early interactions past the ",[30,31,32],"code",{},"200ms"," INP budget.",[15,35,36,37,40],{},"Vue Router supports lazy routes natively: pass a function returning a dynamic import as the route's ",[30,38,39],{},"component",", and the bundler emits a separate chunk loaded on first navigation. The decisions that matter are which routes to split, how to group them, which to keep eager, and how to keep first visits to split routes fast.",[15,42,43],{},[44,45,51,52,51,59,51,63,51,66,51,75,51,81,51,88,51,98,51,103,51,107,51,113,51,117,51,121,51,125,51,129,51],"svg",{"viewBox":46,"width":47,"role":48,"ariaLabel":49,"style":50},"0 0 760 189","100%","img","Bar chart of initial JavaScript for one Vue application under three route-splitting strategies.","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",{},"Initial JavaScript for a Vue app by splitting strategy",[64,65,49],"desc",{},[53,67],{"x":68,"y":68,"width":69,"height":70,"rx":71,"fill":72,"stroke":73,"style":74},"1","758","187","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",[76,82,87],{"x":83,"y":84,"fill":73,"style":85,"textAnchor":86},"208.8","70.0","font-size:13px","end","All routes static",[53,89],{"x":90,"y":91,"width":92,"height":93,"rx":94,"fill":95,"stroke":96,"style":97},"220.8","56.0","447.2","19","3","#ffc300","#b8860b","fill-opacity:0.7;stroke-opacity:0.9",[76,99,102],{"x":100,"y":84,"fill":73,"style":101},"674.0","font-size:12px;font-weight:600","540KB",[76,104,106],{"x":83,"y":105,"fill":73,"style":85,"textAnchor":86},"101.0","Every route lazy",[53,108],{"x":90,"y":109,"width":110,"height":93,"rx":94,"fill":111,"stroke":111,"style":112},"87.0","169.8","#0466c8","fill-opacity:0.55;stroke-opacity:0.9",[76,114,116],{"x":115,"y":105,"fill":73,"style":101},"396.6","205KB",[76,118,120],{"x":83,"y":119,"fill":73,"style":85,"textAnchor":86},"132.0","Lazy + grouped + landing eager",[53,122],{"x":90,"y":123,"width":124,"height":93,"rx":94,"fill":111,"stroke":111,"style":112},"118.0","177.2",[76,126,128],{"x":127,"y":119,"fill":73,"style":101},"404.0","214KB",[76,130,134],{"x":78,"y":131,"fill":132,"style":133},"167.0","#51617a","font-size:12.5px","Grouped splitting keeps initial size nearly as small while avoiding dozens of tiny chunks and a waterfall on the landing route.",[136,137,139],"h2",{"id":138},"rapid-diagnosis","Rapid Diagnosis",[141,142,143,158,164,170],"ul",{},[144,145,146,51,150,153,154,157],"li",{},[147,148,149],"strong",{},"Check route definitions.",[30,151,152],{},"component: HomeView"," (static import) means the route is in the main bundle; ",[30,155,156],{},"component: () => import('.\u002Fviews\u002FHomeView.vue')"," means it is split.",[144,159,160,163],{},[147,161,162],{},"Look at the bundle treemap"," for views that appear in the entry chunk.",[144,165,166,169],{},[147,167,168],{},"Check the landing route."," If it is lazy, the first load fetches the entry, then the route chunk — a waterfall on the most important navigation.",[144,171,172,175],{},[147,173,174],{},"Watch first visits to split routes"," in a throttled trace: is the chunk requested on click?",[136,177,179],{"id":178},"root-cause-analysis","Root Cause Analysis",[15,181,182,185],{},[147,183,184],{},"1. Static imports of views."," The default scaffolding often imports views statically for simplicity.",[15,187,188,191],{},[147,189,190],{},"2. Lazy landing route."," Splitting the route users land on adds a round trip to LCP for client-rendered apps.",[15,193,194,197],{},[147,195,196],{},"3. Over-splitting."," Every small view in its own chunk produces many requests and loading flashes during navigation.",[15,199,200,203],{},[147,201,202],{},"4. No prefetching."," Lazy routes without prefetch make every first visit wait for the chunk.",[15,205,206],{},[44,207,51,210,51,213,51,216,51,218,51,221,51,223,51,229,51,235,51,241,51,245,51,249,51,252,51,255,51,259,51,262,51,266,51,270,51],{"viewBox":208,"width":47,"role":48,"ariaLabel":209,"style":50},"0 0 760 268","Grouping of routes into chunks, from the eager landing route to rarely used admin routes.",[53,211],{"className":212,"x":57,"y":57,"width":47,"height":47,"fill":58},[56],[60,214,215],{},"Route chunk plan for a typical app",[64,217,209],{},[53,219],{"x":68,"y":68,"width":69,"height":220,"rx":71,"fill":72,"stroke":73,"style":74},"266",[76,222,215],{"x":78,"y":79,"fill":73,"style":80},[53,224],{"x":78,"y":91,"width":225,"height":226,"rx":227,"fill":111,"stroke":111,"style":228},"704.0","40.0","6","fill-opacity:0.14;stroke-opacity:0.9",[76,230,234],{"x":231,"y":232,"fill":73,"style":233},"42.0","80.5","font-size:13px;font-weight:700","Entry (eager)",[76,236,240],{"x":237,"y":232,"fill":73,"style":238,"textAnchor":239},"149.7","font-size:12px","start","app shell, router, landing route (home or dashboard)",[53,242],{"x":78,"y":243,"width":225,"height":226,"rx":227,"fill":95,"stroke":96,"style":244},"106.0","fill-opacity:0.24;stroke-opacity:0.9",[76,246,248],{"x":231,"y":247,"fill":73,"style":233},"130.5","Core group",[76,250,251],{"x":237,"y":247,"fill":73,"style":238,"textAnchor":239},"frequent next steps: search, product, cart — one chunk, prefetched",[53,253],{"x":78,"y":254,"width":225,"height":226,"rx":227,"fill":111,"stroke":111,"style":228},"156.0",[76,256,258],{"x":231,"y":257,"fill":73,"style":233},"180.5","Account group",[76,260,261],{"x":237,"y":257,"fill":73,"style":238,"textAnchor":239},"profile, orders, settings — one chunk, loaded on demand",[53,263],{"x":78,"y":264,"width":225,"height":226,"rx":227,"fill":73,"stroke":73,"style":265},"206.0","fill-opacity:0.06;stroke-opacity:0.4",[76,267,269],{"x":231,"y":268,"fill":73,"style":233},"230.5","Admin group",[76,271,272],{"x":237,"y":268,"fill":73,"style":238,"textAnchor":239},"admin-only views — separate chunk, never loaded for most users",[136,274,276],{"id":275},"step-by-step-resolution","Step-by-Step Resolution",[278,279,281],"h3",{"id":280},"_1-make-non-landing-routes-lazy","1. Make non-landing routes lazy",[283,284,289],"pre",{"className":285,"code":286,"language":287,"meta":288,"style":288},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u002F\u002F router.js\nimport { createRouter, createWebHistory } from 'vue-router';\nimport HomeView from '.\u002Fviews\u002FHomeView.vue';                     \u002F\u002F landing: eager\n\nexport default createRouter({\n  history: createWebHistory(),\n  routes: [\n    { path: '\u002F', component: HomeView },\n    { path: '\u002Fsearch', component: () => import('.\u002Fviews\u002FSearchView.vue') },\n    { path: '\u002Fproduct\u002F:id', component: () => import('.\u002Fviews\u002FProductView.vue') },\n    { path: '\u002Faccount', component: () => import('.\u002Fviews\u002FAccountView.vue') },\n  ],\n});\n\u002F\u002F trade-off: if users land on many different routes (deep links from search),\n\u002F\u002F \"the landing route\" is not one route. Server rendering (Nuxt) or eager loading\n\u002F\u002F of the top two or three landing templates is the better answer then.\n","javascript","",[30,290,291,300,321,340,347,364,379,385,397,428,453,478,486,494,500,506],{"__ignoreMap":288},[292,293,296],"span",{"class":294,"line":295},"line",1,[292,297,299],{"class":298},"sjfSM","\u002F\u002F router.js\n",[292,301,303,307,311,314,318],{"class":294,"line":302},2,[292,304,306],{"class":305},"sPARh","import",[292,308,310],{"class":309},"saISM"," { createRouter, createWebHistory } ",[292,312,313],{"class":305},"from",[292,315,317],{"class":316},"sZ8jY"," 'vue-router'",[292,319,320],{"class":309},";\n",[292,322,324,326,329,331,334,337],{"class":294,"line":323},3,[292,325,306],{"class":305},[292,327,328],{"class":309}," HomeView ",[292,330,313],{"class":305},[292,332,333],{"class":316}," '.\u002Fviews\u002FHomeView.vue'",[292,335,336],{"class":309},";                     ",[292,338,339],{"class":298},"\u002F\u002F landing: eager\n",[292,341,343],{"class":294,"line":342},4,[292,344,346],{"emptyLinePlaceholder":345},true,"\n",[292,348,350,353,356,360],{"class":294,"line":349},5,[292,351,352],{"class":305},"export",[292,354,355],{"class":305}," default",[292,357,359],{"class":358},"smZ65"," createRouter",[292,361,363],{"class":362},"sQw3B","({\n",[292,365,367,370,373,376],{"class":294,"line":366},6,[292,368,369],{"class":309},"  history: ",[292,371,372],{"class":358},"createWebHistory",[292,374,375],{"class":309},"()",[292,377,378],{"class":362},",\n",[292,380,382],{"class":294,"line":381},7,[292,383,384],{"class":309},"  routes: [\n",[292,386,388,391,394],{"class":294,"line":387},8,[292,389,390],{"class":309},"    { path: ",[292,392,393],{"class":316},"'\u002F'",[292,395,396],{"class":309},", component: HomeView },\n",[292,398,400,402,405,408,410,413,416,419,422,425],{"class":294,"line":399},9,[292,401,390],{"class":309},[292,403,404],{"class":316},"'\u002Fsearch'",[292,406,407],{"class":309},", ",[292,409,39],{"class":358},[292,411,412],{"class":309},": () ",[292,414,415],{"class":305},"=>",[292,417,418],{"class":305}," import",[292,420,421],{"class":309},"(",[292,423,424],{"class":316},"'.\u002Fviews\u002FSearchView.vue'",[292,426,427],{"class":309},") },\n",[292,429,431,433,436,438,440,442,444,446,448,451],{"class":294,"line":430},10,[292,432,390],{"class":309},[292,434,435],{"class":316},"'\u002Fproduct\u002F:id'",[292,437,407],{"class":309},[292,439,39],{"class":358},[292,441,412],{"class":309},[292,443,415],{"class":305},[292,445,418],{"class":305},[292,447,421],{"class":309},[292,449,450],{"class":316},"'.\u002Fviews\u002FProductView.vue'",[292,452,427],{"class":309},[292,454,456,458,461,463,465,467,469,471,473,476],{"class":294,"line":455},11,[292,457,390],{"class":309},[292,459,460],{"class":316},"'\u002Faccount'",[292,462,407],{"class":309},[292,464,39],{"class":358},[292,466,412],{"class":309},[292,468,415],{"class":305},[292,470,418],{"class":305},[292,472,421],{"class":309},[292,474,475],{"class":316},"'.\u002Fviews\u002FAccountView.vue'",[292,477,427],{"class":309},[292,479,481,484],{"class":294,"line":480},12,[292,482,483],{"class":309},"  ]",[292,485,378],{"class":362},[292,487,489,492],{"class":294,"line":488},13,[292,490,491],{"class":362},"})",[292,493,320],{"class":309},[292,495,497],{"class":294,"line":496},14,[292,498,499],{"class":298},"\u002F\u002F trade-off: if users land on many different routes (deep links from search),\n",[292,501,503],{"class":294,"line":502},15,[292,504,505],{"class":298},"\u002F\u002F \"the landing route\" is not one route. Server rendering (Nuxt) or eager loading\n",[292,507,509],{"class":294,"line":508},16,[292,510,511],{"class":298},"\u002F\u002F of the top two or three landing templates is the better answer then.\n",[15,513,514],{},"Expected outcome: views not needed for the first render leave the entry chunk.",[278,516,518],{"id":517},"_2-group-related-routes-into-shared-chunks","2. Group related routes into shared chunks",[15,520,521,522,525],{},"With Vite, use a ",[30,523,524],{},"manualChunks"," rule by path; with webpack, use magic comments.",[283,527,529],{"className":285,"code":528,"language":287,"meta":288,"style":288},"\u002F\u002F webpack: name chunks so related routes share one.\n{ path: '\u002Faccount', component: () => import(\u002F* webpackChunkName: \"account\" *\u002F '.\u002Fviews\u002FAccountView.vue') },\n{ path: '\u002Faccount\u002Forders', component: () => import(\u002F* webpackChunkName: \"account\" *\u002F '.\u002Fviews\u002FOrdersView.vue') },\n\n\u002F\u002F Vite: group by directory in vite.config.js\n\u002F\u002F manualChunks(id) { if (id.includes('\u002Fsrc\u002Fviews\u002Faccount\u002F')) return 'account'; }\n\u002F\u002F trade-off: grouping means visiting one account page loads all account views.\n\u002F\u002F Group only routes users typically visit together in a session.\n",[30,530,531,536,569,599,603,608,613,618],{"__ignoreMap":288},[292,532,533],{"class":294,"line":295},[292,534,535],{"class":298},"\u002F\u002F webpack: name chunks so related routes share one.\n",[292,537,538,541,544,547,549,551,553,555,557,559,561,564,567],{"class":294,"line":302},[292,539,540],{"class":309},"{ ",[292,542,543],{"class":362},"path",[292,545,546],{"class":309},": ",[292,548,460],{"class":316},[292,550,407],{"class":309},[292,552,39],{"class":362},[292,554,412],{"class":309},[292,556,415],{"class":305},[292,558,418],{"class":358},[292,560,421],{"class":309},[292,562,563],{"class":298},"\u002F* webpackChunkName: \"account\" *\u002F",[292,565,566],{"class":316}," '.\u002Fviews\u002FAccountView.vue'",[292,568,427],{"class":309},[292,570,571,573,575,577,580,582,584,586,588,590,592,594,597],{"class":294,"line":323},[292,572,540],{"class":309},[292,574,543],{"class":362},[292,576,546],{"class":309},[292,578,579],{"class":316},"'\u002Faccount\u002Forders'",[292,581,407],{"class":309},[292,583,39],{"class":362},[292,585,412],{"class":309},[292,587,415],{"class":305},[292,589,418],{"class":358},[292,591,421],{"class":309},[292,593,563],{"class":298},[292,595,596],{"class":316}," '.\u002Fviews\u002FOrdersView.vue'",[292,598,427],{"class":309},[292,600,601],{"class":294,"line":342},[292,602,346],{"emptyLinePlaceholder":345},[292,604,605],{"class":294,"line":349},[292,606,607],{"class":298},"\u002F\u002F Vite: group by directory in vite.config.js\n",[292,609,610],{"class":294,"line":366},[292,611,612],{"class":298},"\u002F\u002F manualChunks(id) { if (id.includes('\u002Fsrc\u002Fviews\u002Faccount\u002F')) return 'account'; }\n",[292,614,615],{"class":294,"line":381},[292,616,617],{"class":298},"\u002F\u002F trade-off: grouping means visiting one account page loads all account views.\n",[292,619,620],{"class":294,"line":387},[292,621,622],{"class":298},"\u002F\u002F Group only routes users typically visit together in a session.\n",[15,624,625],{},"Expected outcome: fewer, purposeful chunks; navigating within a group needs no further requests.",[278,627,629],{"id":628},"_3-prefetch-likely-next-routes","3. Prefetch likely next routes",[15,631,632],{},"Prefetch the core group after the landing route is interactive, and other groups on hover of their links. Nuxt and many Vite setups emit prefetch hints automatically; in a plain Vue Router app, trigger the import on intent.",[283,634,636],{"className":285,"code":635,"language":287,"meta":288,"style":288},"\u002F\u002F Prefetch the core group when the browser is idle after load.\naddEventListener('load', () => requestIdleCallback(() => {\n  import('.\u002Fviews\u002FSearchView.vue'); import('.\u002Fviews\u002FProductView.vue');\n}));\n\u002F\u002F trade-off: idle prefetch spends bandwidth on users who never navigate. Gate it\n\u002F\u002F on connection type and skip it when saveData is set.\n",[30,637,638,643,669,690,695,700],{"__ignoreMap":288},[292,639,640],{"class":294,"line":295},[292,641,642],{"class":298},"\u002F\u002F Prefetch the core group when the browser is idle after load.\n",[292,644,645,648,650,653,656,658,661,664,666],{"class":294,"line":302},[292,646,647],{"class":358},"addEventListener",[292,649,421],{"class":309},[292,651,652],{"class":316},"'load'",[292,654,655],{"class":309},", () ",[292,657,415],{"class":305},[292,659,660],{"class":358}," requestIdleCallback",[292,662,663],{"class":309},"(() ",[292,665,415],{"class":305},[292,667,668],{"class":309}," {\n",[292,670,671,674,676,678,681,683,685,687],{"class":294,"line":323},[292,672,673],{"class":305},"  import",[292,675,421],{"class":309},[292,677,424],{"class":316},[292,679,680],{"class":309},"); ",[292,682,306],{"class":305},[292,684,421],{"class":309},[292,686,450],{"class":316},[292,688,689],{"class":309},");\n",[292,691,692],{"class":294,"line":342},[292,693,694],{"class":309},"}));\n",[292,696,697],{"class":294,"line":349},[292,698,699],{"class":298},"\u002F\u002F trade-off: idle prefetch spends bandwidth on users who never navigate. Gate it\n",[292,701,702],{"class":294,"line":366},[292,703,704],{"class":298},"\u002F\u002F on connection type and skip it when saveData is set.\n",[15,706,707,708,712],{},"Expected outcome: first visits to core routes feel as fast as cached ones. The general technique is in ",[19,709,711],{"href":710},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002Fprefetching-route-chunks-on-hover-and-viewport\u002F","prefetching route chunks on hover and viewport",".",[278,714,716],{"id":715},"_4-avoid-loading-flashes-during-navigation","4. Avoid loading flashes during navigation",[15,718,719,720,723,724,727],{},"Vue Router waits for lazy components to resolve before completing navigation, so the old view stays visible until the new one is ready. Show progress with a thin top bar driven by ",[30,721,722],{},"beforeEach","\u002F",[30,725,726],{},"afterEach"," rather than replacing the view with a spinner.",[15,729,730],{},[44,731,51,734,51,737,51,740,51,742,51,758,51,761,51,763,51,767,51,773,51,776,51,780,51,783,51,787,51,790,51,794,51,802,51,805,51,808,51,811,51,817,51,822,51,826,51,831,51,835,51,839,51,843,51],{"viewBox":732,"width":47,"role":48,"ariaLabel":733,"style":50},"0 0 760 290","Sequence of a click on a link to a lazy route, the router resolving the component chunk, then rendering the new view.",[53,735],{"className":736,"x":57,"y":57,"width":47,"height":47,"fill":58},[56],[60,738,739],{},"Navigating to a lazy route in Vue Router",[64,741,733],{},[743,744,745],"defs",{},[746,747,754],"marker",{"id":748,"viewBox":749,"refX":750,"refY":751,"markerWidth":752,"markerHeight":752,"orient":753},"fa8633ac96","0 0 10 10","9","5","7","auto-start-reverse",[543,755],{"d":756,"fill":73,"style":757},"M0 0 L10 5 L0 10 z","fill-opacity:0.7",[53,759],{"x":68,"y":68,"width":69,"height":760,"rx":71,"fill":72,"stroke":73,"style":74},"288",[76,762,739],{"x":78,"y":79,"fill":73,"style":80},[53,764],{"x":765,"y":91,"width":766,"height":79,"rx":227,"fill":73,"stroke":73,"style":265},"41.0","150.0",[76,768,772],{"x":769,"y":770,"fill":73,"style":233,"textAnchor":771},"116.0","78.0","middle","User",[53,774],{"x":775,"y":91,"width":766,"height":79,"rx":227,"fill":73,"stroke":73,"style":265},"217.0",[76,777,779],{"x":778,"y":770,"fill":73,"style":233,"textAnchor":771},"292.0","Router",[53,781],{"x":782,"y":91,"width":766,"height":79,"rx":227,"fill":73,"stroke":73,"style":265},"393.0",[76,784,786],{"x":785,"y":770,"fill":73,"style":233,"textAnchor":771},"468.0","Network",[53,788],{"x":789,"y":91,"width":766,"height":79,"rx":227,"fill":73,"stroke":73,"style":265},"569.0",[76,791,793],{"x":792,"y":770,"fill":73,"style":233,"textAnchor":771},"644.0","View",[294,795],{"x1":769,"y1":796,"x2":769,"y2":797,"stroke":73,"strokeWidth":798,"strokeDashArray":799,"style":801},"90.0","268.0","1.5",[800,800],"4","stroke-opacity:0.35",[294,803],{"x1":778,"y1":796,"x2":778,"y2":797,"stroke":73,"strokeWidth":798,"strokeDashArray":804,"style":801},[800,800],[294,806],{"x1":785,"y1":796,"x2":785,"y2":797,"stroke":73,"strokeWidth":798,"strokeDashArray":807,"style":801},[800,800],[294,809],{"x1":792,"y1":796,"x2":792,"y2":797,"stroke":73,"strokeWidth":798,"strokeDashArray":810,"style":801},[800,800],[294,812],{"x1":769,"y1":813,"x2":814,"y2":813,"stroke":73,"strokeWidth":798,"style":815,"markerEnd":816},"124.0","290.0","stroke-opacity:0.6","url(#fa8633ac96)",[76,818,821],{"x":819,"y":820,"fill":73,"style":238,"textAnchor":771},"204.0","117.0","click \u002Faccount",[294,823],{"x1":778,"y1":824,"x2":825,"y2":824,"stroke":73,"strokeWidth":798,"style":815,"markerEnd":816},"164.0","466.0",[76,827,830],{"x":828,"y":829,"fill":73,"style":238,"textAnchor":771},"380.0","157.0","GET account.js",[294,832],{"x1":785,"y1":819,"x2":833,"y2":819,"stroke":73,"strokeWidth":798,"strokeDashArray":834,"style":815,"markerEnd":816},"294.0",[751,800],[76,836,838],{"x":828,"y":837,"fill":73,"style":238,"textAnchor":771},"197.0","chunk resolved",[294,840],{"x1":778,"y1":841,"x2":842,"y2":841,"stroke":73,"strokeWidth":798,"style":815,"markerEnd":816},"244.0","642.0",[76,844,846],{"x":785,"y":845,"fill":73,"style":238,"textAnchor":771},"237.0","render AccountView",[136,848,850],{"id":849},"verification","Verification",[15,852,853],{},"Check the treemap: views appear in their group chunks, not the entry. In a throttled trace of the landing page, only the entry (and its static dependencies) should load. Navigate to a prefetched route: no chunk request should occur at click. Compare TBT and route transition times before and after; the landing route's TBT should drop roughly in proportion to the removed view code.",[136,855,857],{"id":856},"worked-example-an-e-commerce-spa","Worked Example: An E-Commerce SPA",[15,859,860],{},"A Vue storefront imported its 34 views statically; the entry chunk was 540KB uncompressed and lab TBT on the product listing (the main landing route) was 620ms. The team made all views lazy except the listing, grouped account and admin views, and grouped product, cart and checkout into one \"shop\" chunk prefetched at idle. The entry fell to 214KB, TBT to 290ms, and field INP p75 for interactions in the first five seconds improved from 260ms to 180ms. Transitions into the shop group were unchanged because of the idle prefetch; the first visit to account pages added about 250ms on mobile, which the team accepted given how rarely those pages were the first navigation.",[136,862,864],{"id":863},"common-mistakes","Common Mistakes",[141,866,867,873,882,888],{},[144,868,869,872],{},[147,870,871],{},"Lazy-loading layout components."," Shared layouts used by every route belong in the entry; making them lazy delays every navigation.",[144,874,875,51,878,881],{},[147,876,877],{},"Using async components inside templates instead of at route level.",[30,879,880],{},"defineAsyncComponent"," is useful for heavy widgets, but route-level splitting is the main lever.",[144,883,884,887],{},[147,885,886],{},"Forgetting chunk load error handling."," After a deploy, old tabs may request chunks that no longer exist.",[144,889,890,893],{},[147,891,892],{},"Grouping by file structure instead of user journeys."," Directory layout rarely matches navigation patterns.",[136,895,897],{"id":896},"route-level-data-loading-alongside-chunks","Route-Level Data Loading Alongside Chunks",[15,899,900,901,904,905,908,909,912],{},"Splitting code solves half of the transition delay; the other half is usually data. If each view fetches its data in ",[30,902,903],{},"onMounted",", the sequence is chunk → mount → fetch → render: a waterfall. Start the data request in a navigation guard (",[30,906,907],{},"beforeResolve"," or a per-route ",[30,910,911],{},"beforeEnter",") in parallel with the chunk, and pass the promise to the view. Combined with chunk prefetching, a well-structured Vue app can transition to a data-heavy route in roughly the time of one API round trip.",[283,914,916],{"className":285,"code":915,"language":287,"meta":288,"style":288},"router.beforeResolve(async (to) => {\n  if (to.meta.load) to.meta.data = to.meta.load(to.params);   \u002F\u002F start fetch, do not await\n});\n\u002F\u002F trade-off: not awaiting means the view must handle a pending promise. Await\n\u002F\u002F it here instead if you prefer the old view to stay until data is ready.\n",[30,917,918,943,966,971,976],{"__ignoreMap":288},[292,919,920,923,925,927,930,933,936,939,941],{"class":294,"line":295},[292,921,922],{"class":309},"router.",[292,924,907],{"class":358},[292,926,421],{"class":309},[292,928,929],{"class":305},"async",[292,931,932],{"class":309}," (",[292,934,935],{"class":362},"to",[292,937,938],{"class":309},") ",[292,940,415],{"class":305},[292,942,668],{"class":309},[292,944,945,948,951,954,957,960,963],{"class":294,"line":302},[292,946,947],{"class":305},"  if",[292,949,950],{"class":309}," (to.meta.load) to.meta.data ",[292,952,953],{"class":305},"=",[292,955,956],{"class":309}," to.meta.",[292,958,959],{"class":358},"load",[292,961,962],{"class":309},"(to.params);   ",[292,964,965],{"class":298},"\u002F\u002F start fetch, do not await\n",[292,967,968],{"class":294,"line":323},[292,969,970],{"class":309},"});\n",[292,972,973],{"class":294,"line":342},[292,974,975],{"class":298},"\u002F\u002F trade-off: not awaiting means the view must handle a pending promise. Await\n",[292,977,978],{"class":294,"line":349},[292,979,980],{"class":298},"\u002F\u002F it here instead if you prefer the old view to stay until data is ready.\n",[136,982,984],{"id":983},"faq","FAQ",[986,987,990,994],"details",{"className":988},[989],"faq-item",[991,992,993],"summary",{},"Does Nuxt split routes automatically?",[15,995,996,997,1000,1001,712],{},"Yes. Nuxt generates a chunk per page and handles prefetching of linked pages through ",[30,998,999],{},"\u003CNuxtLink>",". The decisions in this guide still apply to grouping and to which heavy components inside pages should be lazy — see ",[19,1002,1004],{"href":1003},"\u002Fframework-performance\u002Fvue-and-nuxt-performance\u002F","Vue & Nuxt performance",[986,1006,1008,1011],{"className":1007},[989],[991,1009,1010],{},"Should every route be lazy in a server-rendered app?",[15,1012,1013],{},"In SSR, the initial HTML already shows the landing route, and the client loads that route's chunk to hydrate it. Lazy routes are still beneficial — they keep other routes out of the initial download — and frameworks preload the current route's chunk so there is no waterfall.",[986,1015,1017,1020],{"className":1016},[989],[991,1018,1019],{},"What about defineAsyncComponent for heavy widgets?",[15,1021,1022,1023,1026],{},"It is the Vue equivalent of ",[30,1024,1025],{},"React.lazy"," for components inside a route. Use it for editors, charts and maps that are not visible initially, with a correctly sized loading component and a delay option so fast loads do not flash a placeholder.",[136,1028,1030],{"id":1029},"related","Related",[141,1032,1033,1040,1047],{},[144,1034,1035,1039],{},[19,1036,1038],{"href":1037},"\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"," — the React\u002FNext.js counterpart.",[144,1041,1042,1046],{},[19,1043,1045],{"href":1044},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fvite-and-rollup-build-optimization\u002Fconfiguring-manualchunks-in-vite\u002F","Configuring manualChunks in Vite"," — grouping route chunks in Vite.",[144,1048,1049,1053],{},[19,1050,1052],{"href":1051},"\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002Fbreaking-up-long-tasks-in-vue-event-handlers\u002F","Breaking up long tasks in Vue event handlers"," — the runtime half of Vue INP work.",[1055,1056,1058],"script",{"type":1057},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"How to Code-Split Vue Router Routes\",\n  \"description\": \"How to configure lazy routes in Vue Router with Vite or webpack, choose chunk boundaries, and avoid the waterfalls and loading flashes that hurt UX.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Make non-landing routes lazy\",\n      \"text\": \"export default createRouter({ history: createWebHistory(), routes: [ { path: '\u002F', component: HomeView }, { path: '\u002Fsearch', component: () => import('.\u002Fviews\u002FSearchView.vue') }, { path: '\u002Fproduct\u002F:id', component: () => import('.\u002Fviews\u002FProductView.vue') }, { path: '\u002Faccount', component: () => import('\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Group related routes into shared chunks\",\n      \"text\": \"With Vite, use a manualChunks rule by path; with webpack, use magic comments.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Prefetch likely next routes\",\n      \"text\": \"Prefetch the core group after the landing route is interactive, and other groups on hover of their links.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Avoid loading flashes during navigation\",\n      \"text\": \"Vue Router waits for lazy components to resolve before completing navigation, so the old view stays visible until the new one is ready.\"\n    }\n  ]\n}\n",[1055,1060,1061],{"type":1057},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"How to Code-Split Vue Router Routes\",\n  \"description\": \"How to configure lazy routes in Vue Router with Vite or webpack, choose chunk boundaries, and avoid the waterfalls and loading flashes that hurt UX.\",\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\u002Fdynamic-imports-and-route-based-splitting\u002Fcode-splitting-vue-router-routes\u002F\"\n  }\n}\n",[1055,1063,1064],{"type":1057},"\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\": \"Dynamic Imports and Route-Based Splitting\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 4,\n      \"name\": \"Code-Splitting Vue Router Routes\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fjavascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002Fcode-splitting-vue-router-routes\u002F\"\n    }\n  ]\n}\n",[1066,1067,1068],"style",{},"html pre.shiki code .sjfSM, html code.shiki .sjfSM{--shiki-default:#66707B;--shiki-dark:#BDC4CC;--shiki-light:#66707B}html pre.shiki code .sPARh, html code.shiki .sPARh{--shiki-default:#A0111F;--shiki-dark:#FF9492;--shiki-light:#A0111F}html pre.shiki code .saISM, html code.shiki .saISM{--shiki-default:#0E1116;--shiki-dark:#F0F3F6;--shiki-light:#0E1116}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}html pre.shiki code .smZ65, html code.shiki .smZ65{--shiki-default:#622CBC;--shiki-dark:#DBB7FF;--shiki-light:#622CBC}html pre.shiki code .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}",{"title":288,"searchDepth":302,"depth":302,"links":1070},[1071,1072,1073,1079,1080,1081,1082,1083,1084],{"id":138,"depth":302,"text":139},{"id":178,"depth":302,"text":179},{"id":275,"depth":302,"text":276,"children":1074},[1075,1076,1077,1078],{"id":280,"depth":323,"text":281},{"id":517,"depth":323,"text":518},{"id":628,"depth":323,"text":629},{"id":715,"depth":323,"text":716},{"id":849,"depth":302,"text":850},{"id":856,"depth":302,"text":857},{"id":863,"depth":302,"text":864},{"id":896,"depth":302,"text":897},{"id":983,"depth":302,"text":984},{"id":1029,"depth":302,"text":1030},"How to configure lazy routes in Vue Router with Vite or webpack, choose chunk boundaries, and avoid the waterfalls and loading flashes that hurt UX.","md",{"slug":1088,"type":1089,"breadcrumb":1090,"datePublished":1097,"dateModified":1097},"code-splitting-vue-router-routes","article",[1091,1093,1094,1095],{"name":1092,"url":723},"Home",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":1096},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002Fcode-splitting-vue-router-routes\u002F","2026-10-06","\u002Fjavascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002Fcode-splitting-vue-router-routes",{"title":1100,"description":1101},"Code-Splitting Vue Router Routes for Faster Loads","Split Vue Router routes into lazy chunks, group related routes, keep the landing route eager, and prefetch next routes so transitions stay fast.","javascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002Fcode-splitting-vue-router-routes\u002Findex","aQIzzP9ubb9CKomcALCZ4fLH2W5VExY_bSDMwk9MpD8",[1105,1108],{"title":22,"path":1106,"stem":1107,"children":-1},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting","javascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002Findex",{"title":1109,"path":1110,"stem":1111,"children":-1},"Fixing Waterfalls from Nested Dynamic Imports","\u002Fjavascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002Ffixing-waterfalls-from-nested-dynamic-imports","javascript-bundle-optimization-code-splitting\u002Fdynamic-imports-and-route-based-splitting\u002Ffixing-waterfalls-from-nested-dynamic-imports\u002Findex",1791308070462]