[{"data":1,"prerenderedAt":1282},["ShallowReactive",2],{"content:\u002Fframework-performance\u002Fvue-and-nuxt-performance":3,"surroundings:\u002Fframework-performance\u002Fvue-and-nuxt-performance":1273},{"id":4,"title":5,"body":6,"description":1254,"extension":1255,"meta":1256,"navigation":1266,"path":1267,"seo":1268,"stem":1271,"__hash__":1272},"content\u002Fframework-performance\u002Fvue-and-nuxt-performance\u002Findex.md","Vue & Nuxt Performance",{"type":7,"value":8,"toc":1230},"minimark",[9,14,24,44,149,154,183,187,215,219,303,307,310,314,317,321,351,456,520,524,532,535,562,566,579,585,591,597,603,607,610,662,666,675,679,699,703,724,851,855,878,882,907,911,921,928,932,947,951,958,962,986,990,1022,1026,1043,1056,1065,1074,1083,1092,1101,1116,1125,1137,1146,1155,1159,1189,1193,1215,1220,1223,1226],[10,11,13],"h1",{"id":12},"vue-nuxt-performance-fast-pages-with-less-hydration","Vue & Nuxt Performance: Fast Pages With Less Hydration",[15,16,17,18,23],"p",{},"This topic is part of ",[19,20,22],"a",{"href":21},"\u002Fframework-performance\u002F","Framework Performance",". Vue's fine-grained reactivity tracks exactly which components depend on which state, so updates are usually targeted and cheap. Nuxt builds on Vue with server-side rendering, static generation, hybrid rendering per route, automatic code splitting, data fetching composables, and modules for images, fonts and scripts. Out of the box, a Nuxt site renders HTML on the server and hydrates it on the client, which gives good LCP when configured well.",[15,25,26,27,31,32,35,36,39,40,43],{},"The performance problems in Vue and Nuxt apps tend to cluster in four places. ",[28,29,30],"strong",{},"Payload size",": data fetched on the server is serialised into the HTML (the Nuxt payload) for hydration, and it often includes far more than the page uses. ",[28,33,34],{},"Hydration cost",": every component on the page hydrates, including large static sections. ",[28,37,38],{},"Reactivity overhead",": deep reactive proxies over large data structures cost memory and time to create and track. ",[28,41,42],{},"Rendering strategy",": routes rendered on every request that could be static or cached. Each has a direct, framework-native fix.",[15,45,46],{},[47,48,54,55,54,62,54,66,54,69,54,78,54,84,54,92,54,99,54,104,54,108,54,112,54,115,54,119,54,123,54,126,54,129,54,133,54,136,54,142,54,146,54],"svg",{"viewBox":49,"width":50,"role":51,"ariaLabel":52,"style":53},"0 0 760 318","100%","img","Layers of a Nuxt page and the framework features that optimise each.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[56,57],"rect",{"className":58,"x":60,"y":60,"width":50,"height":50,"fill":61},[59],"svg-canvas","0","#ffffff",[63,64,65],"title",{},"Nuxt performance levers by layer",[67,68,52],"desc",{},[56,70],{"x":71,"y":71,"width":72,"height":73,"rx":74,"fill":75,"stroke":76,"style":77},"1","758","316","10","none","currentColor","stroke-opacity:0.18",[79,80,65],"text",{"x":81,"y":82,"fill":76,"style":83},"28.0","34.0","font-size:16px;font-weight:700",[56,85],{"x":81,"y":86,"width":87,"height":88,"rx":89,"fill":90,"stroke":90,"style":91},"56.0","704.0","40.0","6","#0466c8","fill-opacity:0.14;stroke-opacity:0.9",[79,93,98],{"x":94,"y":95,"fill":76,"style":96,"textAnchor":97},"42.0","80.5","font-size:13px;font-weight:700","start","Route rules",[79,100,103],{"x":101,"y":95,"fill":76,"style":102,"textAnchor":97},"142.8","font-size:12px","Prerender, cache (SWR\u002FISR), SSR or client per route",[56,105],{"x":81,"y":106,"width":87,"height":88,"rx":89,"fill":76,"stroke":76,"style":107},"106.0","fill-opacity:0.06;stroke-opacity:0.4",[79,109,111],{"x":94,"y":110,"fill":76,"style":96,"textAnchor":97},"130.5","Data fetching",[79,113,114],{"x":101,"y":110,"fill":76,"style":102,"textAnchor":97},"useFetch \u002F useAsyncData with pick, transform, lazy",[56,116],{"x":81,"y":117,"width":87,"height":88,"rx":89,"fill":75,"stroke":76,"style":118},"156.0","stroke-opacity:0.35",[79,120,122],{"x":94,"y":121,"fill":76,"style":96,"textAnchor":97},"180.5","Payload",[79,124,125],{"x":101,"y":121,"fill":76,"style":102,"textAnchor":97},"Only the data the page renders",[56,127],{"x":81,"y":128,"width":87,"height":88,"rx":89,"fill":75,"stroke":76,"style":118},"206.0",[79,130,132],{"x":94,"y":131,"fill":76,"style":96,"textAnchor":97},"230.5","Hydration",[79,134,135],{"x":101,"y":131,"fill":76,"style":102,"textAnchor":97},"Lazy hydration, server components, client-only islands",[56,137],{"x":81,"y":138,"width":87,"height":88,"rx":89,"fill":139,"stroke":140,"style":141},"256.0","#ffc300","#b8860b","fill-opacity:0.24;stroke-opacity:0.9",[79,143,145],{"x":94,"y":144,"fill":76,"style":96,"textAnchor":97},"280.5","Reactivity",[79,147,148],{"x":101,"y":144,"fill":76,"style":102,"textAnchor":97},"shallowRef, markRaw, computed caching",[150,151,153],"h2",{"id":152},"the-metric-degradation-this-topic-addresses","The Metric Degradation This Topic Addresses",[15,155,156,157,160,161,164,165,167,168,171,172,175,176,178,179,182],{},"Large payloads increase HTML size (slower TTFB-to-FCP transfer) and the time to parse and revive state during hydration, adding to ",[28,158,159],{},"TBT"," and early ",[28,162,163],{},"INP",". Full hydration of large pages produces long tasks after load. Deep reactivity over large datasets slows initial rendering and updates, hurting ",[28,166,163],{}," for interactions on data-heavy pages (tables, dashboards). Dynamic rendering of cacheable routes inflates ",[28,169,170],{},"TTFB"," and therefore ",[28,173,174],{},"LCP",". Images and fonts handled without Nuxt Image or Nuxt Fonts add ",[28,177,174],{}," and ",[28,180,181],{},"CLS"," problems.",[150,184,186],{"id":185},"prerequisites","Prerequisites",[188,189,190,198,209,212],"ul",{},[191,192,193,197],"li",{},[194,195,196],"code",{},"nuxi analyze"," or a Vite bundle visualiser for client bundles.",[191,199,200,201,204,205,208],{},"The ability to inspect the payload (view source, look for ",[194,202,203],{},"__NUXT_DATA__",", or the ",[194,206,207],{},"_payload.json"," for prerendered routes).",[191,210,211],{},"Vue DevTools with performance timeline, and the browser Performance panel.",[191,213,214],{},"Field metrics per route.",[150,216,218],{"id":217},"_1-environment-setup-inspect-payload-and-bundles","1. Environment Setup: Inspect Payload and Bundles",[220,221,226],"pre",{"className":222,"code":223,"language":224,"meta":225,"style":225},"language-bash shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","# Build analysis for a Nuxt app (opens a visualiser of client and server bundles).\nnpx nuxi analyze\n# Payload size for a page (look for the serialized data script).\ncurl -s https:\u002F\u002Fexample.com\u002Fproducts\u002F42 | grep -o '\u003Cscript[^>]*id=\"__NUXT_DATA__\"[^>]*>.*\u003C\u002Fscript>' | wc -c\n# trade-off: payload size varies per page; check representative pages with large\n# data (listings, dashboards) rather than only the homepage.\n","bash","",[194,227,228,237,251,257,291,297],{"__ignoreMap":225},[229,230,233],"span",{"class":231,"line":232},"line",1,[229,234,236],{"class":235},"sjfSM","# Build analysis for a Nuxt app (opens a visualiser of client and server bundles).\n",[229,238,240,244,248],{"class":231,"line":239},2,[229,241,243],{"class":242},"sQw3B","npx",[229,245,247],{"class":246},"sZ8jY"," nuxi",[229,249,250],{"class":246}," analyze\n",[229,252,254],{"class":231,"line":253},3,[229,255,256],{"class":235},"# Payload size for a page (look for the serialized data script).\n",[229,258,260,263,267,270,274,277,280,283,285,288],{"class":231,"line":259},4,[229,261,262],{"class":242},"curl",[229,264,266],{"class":265},"sPXB4"," -s",[229,268,269],{"class":246}," https:\u002F\u002Fexample.com\u002Fproducts\u002F42",[229,271,273],{"class":272},"sPARh"," |",[229,275,276],{"class":242}," grep",[229,278,279],{"class":265}," -o",[229,281,282],{"class":246}," '\u003Cscript[^>]*id=\"__NUXT_DATA__\"[^>]*>.*\u003C\u002Fscript>'",[229,284,273],{"class":272},[229,286,287],{"class":242}," wc",[229,289,290],{"class":265}," -c\n",[229,292,294],{"class":231,"line":293},5,[229,295,296],{"class":235},"# trade-off: payload size varies per page; check representative pages with large\n",[229,298,300],{"class":231,"line":299},6,[229,301,302],{"class":235},"# data (listings, dashboards) rather than only the homepage.\n",[150,304,306],{"id":305},"_2-capture-a-baseline","2. Capture a Baseline",[15,308,309],{},"Record HTML and payload size, client JavaScript per route, hydration time in a throttled trace, and field LCP\u002FINP per route.",[150,311,313],{"id":312},"_3-isolate-causes","3. Isolate Causes",[15,315,316],{},"Large payload → data fetching returns whole API responses. Long hydration → many components on the page, large static sections hydrating. Slow updates → deep reactive structures, unnecessary watchers, large lists without virtualization. Slow TTFB → routes rendered per request without caching.",[150,318,320],{"id":319},"_4-apply-nuxt-native-fixes","4. Apply Nuxt-Native Fixes",[15,322,323,324,327,328,331,332,335,336,339,340,178,343,346,347,350],{},"Use ",[194,325,326],{},"pick"," or ",[194,329,330],{},"transform"," in ",[194,333,334],{},"useFetch","\u002F",[194,337,338],{},"useAsyncData"," to keep only needed fields. Use lazy hydration for below-the-fold components and server components (or islands) for static sections. Use ",[194,341,342],{},"shallowRef",[194,344,345],{},"markRaw"," for large data. Configure ",[194,348,349],{},"routeRules"," to prerender, cache or SWR routes that do not need per-request rendering.",[220,352,356],{"className":353,"code":354,"language":355,"meta":225,"style":225},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u002F\u002F Only the fields the page renders go into the payload.\nconst { data: product } = await useFetch(`\u002Fapi\u002Fproducts\u002F${id}`, {\n  pick: ['id', 'name', 'price', 'images', 'summary'],\n});\n\u002F\u002F trade-off: pick applies to top-level keys; for nested trimming use transform,\n\u002F\u002F and keep server API responses lean too, since the server still fetches them.\n","javascript",[194,357,358,363,409,441,446,451],{"__ignoreMap":225},[229,359,360],{"class":231,"line":232},[229,361,362],{"class":235},"\u002F\u002F Only the fields the page renders go into the payload.\n",[229,364,365,368,372,375,378,381,384,387,390,394,397,400,403,406],{"class":231,"line":239},[229,366,367],{"class":272},"const",[229,369,371],{"class":370},"saISM"," { ",[229,373,374],{"class":242},"data",[229,376,377],{"class":370},": ",[229,379,380],{"class":265},"product",[229,382,383],{"class":370}," } ",[229,385,386],{"class":272},"=",[229,388,389],{"class":272}," await",[229,391,393],{"class":392},"smZ65"," useFetch",[229,395,396],{"class":370},"(",[229,398,399],{"class":246},"`\u002Fapi\u002Fproducts\u002F${",[229,401,402],{"class":370},"id",[229,404,405],{"class":246},"}`",[229,407,408],{"class":370},", {\n",[229,410,411,414,417,420,423,425,428,430,433,435,438],{"class":231,"line":253},[229,412,413],{"class":370},"  pick: [",[229,415,416],{"class":246},"'id'",[229,418,419],{"class":370},", ",[229,421,422],{"class":246},"'name'",[229,424,419],{"class":370},[229,426,427],{"class":246},"'price'",[229,429,419],{"class":370},[229,431,432],{"class":246},"'images'",[229,434,419],{"class":370},[229,436,437],{"class":246},"'summary'",[229,439,440],{"class":370},"],\n",[229,442,443],{"class":231,"line":259},[229,444,445],{"class":370},"});\n",[229,447,448],{"class":231,"line":293},[229,449,450],{"class":235},"\u002F\u002F trade-off: pick applies to top-level keys; for nested trimming use transform,\n",[229,452,453],{"class":231,"line":299},[229,454,455],{"class":235},"\u002F\u002F and keep server API responses lean too, since the server still fetches them.\n",[15,457,458],{},[47,459,54,462,54,465,54,468,54,470,54,473,54,475,54,482,54,489,54,494,54,498,54,503,54,507,54,511,54,516,54],{"viewBox":460,"width":50,"role":51,"ariaLabel":461,"style":53},"0 0 760 163","Bar chart of serialized payload size in the HTML for a category page before and after trimming data.",[56,463],{"className":464,"x":60,"y":60,"width":50,"height":50,"fill":61},[59],[63,466,467],{},"Nuxt payload size on a category page",[67,469,461],{},[56,471],{"x":71,"y":71,"width":72,"height":472,"rx":74,"fill":75,"stroke":76,"style":77},"161",[79,474,467],{"x":81,"y":82,"fill":76,"style":83},[79,476,481],{"x":477,"y":478,"fill":76,"style":479,"textAnchor":480},"210.8","70.0","font-size:13px","end","Full API response (48 products)",[56,483],{"x":484,"y":86,"width":485,"height":486,"rx":487,"fill":75,"stroke":76,"style":488},"222.8","445.2","19","3","fill-opacity:0.7;stroke-opacity:0.35",[79,490,493],{"x":491,"y":478,"fill":76,"style":492},"674.0","font-size:12px;font-weight:600","410KB",[79,495,497],{"x":477,"y":496,"fill":76,"style":479,"textAnchor":480},"101.0","pick top-level fields",[56,499],{"x":484,"y":500,"width":501,"height":486,"rx":487,"fill":90,"stroke":90,"style":502},"87.0","173.8","fill-opacity:0.55;stroke-opacity:0.9",[79,504,506],{"x":505,"y":496,"fill":76,"style":492},"402.5","160KB",[79,508,510],{"x":477,"y":509,"fill":76,"style":479,"textAnchor":480},"132.0","transform to card fields only",[56,512],{"x":484,"y":513,"width":514,"height":486,"rx":487,"fill":139,"stroke":140,"style":515},"118.0","50.0","fill-opacity:0.7;stroke-opacity:0.9",[79,517,519],{"x":518,"y":509,"fill":76,"style":492},"278.7","46KB",[150,521,523],{"id":522},"deconstructing-nuxt-hydration-and-payload","Deconstructing Nuxt Hydration and Payload",[15,525,526,527,178,529,531],{},"When Nuxt renders a page on the server, data fetched with ",[194,528,334],{},[194,530,338],{}," is stored and serialised into the HTML so the client can hydrate without refetching. The client then loads the JavaScript for the page's components and hydrates them: it runs component setup, rebuilds reactive state from the payload, and attaches event listeners, matching the server-rendered DOM.",[15,533,534],{},"Both parts scale with the page. The payload scales with the data you fetch (not just what you render), and hydration scales with the number of components. A product listing that fetches full product objects (with descriptions, specs, reviews) to render cards with a name, price and image serialises far more than needed. A long article page built from many components hydrates them all, even though only a share button is interactive.",[15,536,537,538,419,540,542,543,546,547,550,551,554,555,550,558,561],{},"Nuxt's answers are data shaping (",[194,539,326],{},[194,541,330],{},", server routes that return only needed fields), ",[28,544,545],{},"lazy hydration"," (delaying hydration of components until visible, idle or interacted with), ",[28,548,549],{},"server components"," (",[194,552,553],{},".server.vue"," components rendered only on the server, with no client JavaScript), and ",[28,556,557],{},"islands",[194,559,560],{},"\u003CNuxtIsland>","), which embed server-rendered components without hydration.",[150,563,565],{"id":564},"advanced-diagnostics-and-edge-cases","Advanced Diagnostics and Edge Cases",[15,567,568,571,572,575,576,578],{},[28,569,570],{},"Duplicate fetching."," Fetching in both server and client because the key differs, or calling ",[194,573,574],{},"$fetch"," directly in setup (which runs on server and client) instead of ",[194,577,334],{},".",[15,580,581,584],{},[28,582,583],{},"Watchers on large objects."," Deep watchers traverse whole structures on every change; watch specific properties.",[15,586,587,590],{},[28,588,589],{},"Plugins running on every page."," Heavy plugins initialise globally; make them lazy or route-specific.",[15,592,593,596],{},[28,594,595],{},"Hydration mismatches."," Mismatches force client re-rendering of affected subtrees, adding work and potential layout shifts. Fix the cause (dates, random values, browser-only data) rather than suppressing warnings.",[15,598,599,602],{},[28,600,601],{},"Third-party modules."," Some modules inject client scripts on every page; audit them with the bundle analysis.",[150,604,606],{"id":605},"validation-and-budgeting","Validation and Budgeting",[15,608,609],{},"After changes, compare payload and client JS sizes, hydration time in throttled traces, and field metrics per route. Budget payload size (for example under 50KB for content pages) and client JS per route in CI.",[611,612,613,626],"table",{},[614,615,616],"thead",{},[617,618,619,623],"tr",{},[620,621,622],"th",{},"Budget",[620,624,625],{},"Target",[627,628,629,638,646,654],"tbody",{},[617,630,631,635],{},[632,633,634],"td",{},"Payload per page (compressed HTML share)",[632,636,637],{},"\u003C 50 KB",[617,639,640,643],{},[632,641,642],{},"Client JS per route (compressed)",[632,644,645],{},"\u003C 120 KB",[617,647,648,651],{},[632,649,650],{},"Hydration long task (mobile)",[632,652,653],{},"\u003C 150 ms",[617,655,656,659],{},[632,657,658],{},"Cacheable routes",[632,660,661],{},"Prerendered or SWR via routeRules",[150,663,665],{"id":664},"worked-example-a-fashion-retailer-on-nuxt","Worked Example: A Fashion Retailer on Nuxt",[15,667,668,669,671,672,674],{},"A fashion retailer's Nuxt category pages fetched 48 full product objects per page; the payload was 420KB in the HTML, and hydration took 600ms on a mid-range phone. The page also rendered a large static footer, a mega menu and an SEO text block as regular components, all hydrating. The team used ",[194,670,330],{}," to keep only card fields (payload 52KB), made the footer and SEO block server components, lazy-hydrated the mega menu on interaction, and set ",[194,673,349],{}," to cache category pages with SWR for 10 minutes. HTML size dropped by 70%, hydration to 140ms, TTFB p75 from 680ms to 120ms, LCP p75 from 2.9 to 1.8 seconds, and INP p75 from 260ms to 150ms.",[150,676,678],{"id":677},"reactivity-cost-on-data-heavy-pages","Reactivity Cost on Data-Heavy Pages",[15,680,681,682,178,685,688,689,691,692,695,696,698],{},"Vue's ",[194,683,684],{},"ref",[194,686,687],{},"reactive"," wrap objects in proxies deeply: every nested object and array becomes reactive, tracked on access and triggering on change. For large datasets — thousands of rows, big JSON structures, chart data — creating and tracking these proxies takes time and memory, and most of it is unnecessary because the data is replaced wholesale rather than mutated property by property. ",[194,690,342],{}," makes only the top-level reference reactive; replacing ",[194,693,694],{},".value"," triggers updates, while nested properties are plain. ",[194,697,345],{}," excludes objects (third-party instances like map or chart objects) from reactivity entirely. Combined with computed values for derived data and virtualization for long lists, these keep data-heavy pages responsive.",[150,700,702],{"id":701},"rendering-modes-with-route-rules","Rendering Modes With Route Rules",[15,704,705,706,708,709,712,713,327,716,719,720,723],{},"Nuxt's ",[194,707,349],{}," set rendering and caching per route pattern: ",[194,710,711],{},"prerender: true"," for pages generated at build time, ",[194,714,715],{},"swr",[194,717,718],{},"isr"," for cached pages revalidated in the background, ",[194,721,722],{},"ssr: false"," for client-only sections such as dashboards behind login, and cache headers for API routes. A content site can prerender its marketing pages, cache blog and product pages with SWR, and server-render only personalised routes. This often produces larger TTFB improvements than any component-level optimisation.",[15,725,726],{},[47,727,54,730,54,733,54,736,54,738,54,741,54,743,54,747,54,753,54,757,54,762,54,765,54,769,54,772,54,776,54,778,54,780,54,782,54,785,54,788,54,792,54,794,54,797,54,799,54,802,54,805,54,809,54,811,54,814,54,816,54,819,54,822,54,826,54,828,54,830,54,832,54,835,54,837,54,841,54,843,54,846,54,848,54],{"viewBox":728,"width":50,"role":51,"ariaLabel":729,"style":53},"0 0 760 258","Suggested Nuxt route rules for common page types.",[56,731],{"className":732,"x":60,"y":60,"width":50,"height":50,"fill":61},[59],[63,734,735],{},"Route rules by page type",[67,737,729],{},[56,739],{"x":71,"y":71,"width":72,"height":740,"rx":74,"fill":75,"stroke":76,"style":77},"256",[79,742,735],{"x":81,"y":82,"fill":76,"style":83},[56,744],{"x":81,"y":86,"width":745,"height":746,"rx":60,"fill":76,"stroke":76,"style":107},"155.3","30.0",[79,748,752],{"x":749,"y":750,"fill":76,"style":751,"textAnchor":97},"38.0","75.5","font-size:12.5px;font-weight:700","Page type",[56,754],{"x":755,"y":86,"width":756,"height":746,"rx":60,"fill":76,"stroke":76,"style":107},"183.3","274.3",[79,758,761],{"x":759,"y":750,"fill":76,"style":751,"textAnchor":760},"320.5","middle","Route rule",[56,763],{"x":764,"y":86,"width":756,"height":746,"rx":60,"fill":76,"stroke":76,"style":107},"457.7",[79,766,768],{"x":767,"y":750,"fill":76,"style":751,"textAnchor":760},"594.8","Effect",[56,770],{"x":81,"y":771,"width":745,"height":746,"rx":60,"fill":75,"stroke":76,"style":118},"86.0",[79,773,775],{"x":749,"y":774,"fill":76,"style":751,"textAnchor":97},"105.5","Marketing and docs",[56,777],{"x":755,"y":771,"width":756,"height":746,"rx":60,"fill":75,"stroke":76,"style":118},[79,779,711],{"x":759,"y":774,"fill":76,"style":102,"textAnchor":760},[56,781],{"x":764,"y":771,"width":756,"height":746,"rx":60,"fill":90,"stroke":90,"style":91},[79,783,784],{"x":767,"y":774,"fill":76,"style":102,"textAnchor":760},"static HTML at build",[56,786],{"x":81,"y":787,"width":745,"height":746,"rx":60,"fill":75,"stroke":76,"style":118},"116.0",[79,789,791],{"x":749,"y":790,"fill":76,"style":751,"textAnchor":97},"135.5","Blog and product pages",[56,793],{"x":755,"y":787,"width":756,"height":746,"rx":60,"fill":75,"stroke":76,"style":118},[79,795,796],{"x":759,"y":790,"fill":76,"style":102,"textAnchor":760},"swr: 600",[56,798],{"x":764,"y":787,"width":756,"height":746,"rx":60,"fill":90,"stroke":90,"style":91},[79,800,801],{"x":767,"y":790,"fill":76,"style":102,"textAnchor":760},"cached, revalidated in background",[56,803],{"x":81,"y":804,"width":745,"height":746,"rx":60,"fill":75,"stroke":76,"style":118},"146.0",[79,806,808],{"x":749,"y":807,"fill":76,"style":751,"textAnchor":97},"165.5","Search results",[56,810],{"x":755,"y":804,"width":756,"height":746,"rx":60,"fill":75,"stroke":76,"style":118},[79,812,813],{"x":759,"y":807,"fill":76,"style":102,"textAnchor":760},"swr: 60 or SSR",[56,815],{"x":764,"y":804,"width":756,"height":746,"rx":60,"fill":76,"stroke":76,"style":107},[79,817,818],{"x":767,"y":807,"fill":76,"style":102,"textAnchor":760},"short cache by query",[56,820],{"x":81,"y":821,"width":745,"height":746,"rx":60,"fill":75,"stroke":76,"style":118},"176.0",[79,823,825],{"x":749,"y":824,"fill":76,"style":751,"textAnchor":97},"195.5","Account dashboard",[56,827],{"x":755,"y":821,"width":756,"height":746,"rx":60,"fill":75,"stroke":76,"style":118},[79,829,722],{"x":759,"y":824,"fill":76,"style":102,"textAnchor":760},[56,831],{"x":764,"y":821,"width":756,"height":746,"rx":60,"fill":76,"stroke":76,"style":107},[79,833,834],{"x":767,"y":824,"fill":76,"style":102,"textAnchor":760},"client-only, no SSR cost",[56,836],{"x":81,"y":128,"width":745,"height":746,"rx":60,"fill":75,"stroke":76,"style":118},[79,838,840],{"x":749,"y":839,"fill":76,"style":751,"textAnchor":97},"225.5","Checkout",[56,842],{"x":755,"y":128,"width":756,"height":746,"rx":60,"fill":75,"stroke":76,"style":118},[79,844,845],{"x":759,"y":839,"fill":76,"style":102,"textAnchor":760},"SSR (no cache)",[56,847],{"x":764,"y":128,"width":756,"height":746,"rx":60,"fill":76,"stroke":76,"style":107},[79,849,850],{"x":767,"y":839,"fill":76,"style":102,"textAnchor":760},"always fresh",[150,852,854],{"id":853},"images-fonts-and-scripts-in-nuxt","Images, Fonts and Scripts in Nuxt",[15,856,857,858,178,861,864,865,178,868,871,872,178,874,877],{},"Nuxt Image (",[194,859,860],{},"\u003CNuxtImg>",[194,862,863],{},"\u003CNuxtPicture>",") generates responsive sizes and modern formats through providers (IPX locally or image CDNs) and supports ",[194,866,867],{},"preload",[194,869,870],{},"fetchpriority"," for the LCP image. Nuxt Fonts self-hosts web fonts and generates metric-adjusted fallbacks, removing third-party font requests and reducing CLS. Nuxt Scripts loads third-party scripts with triggers (on idle, on interaction, on visibility) and facades for embeds. Configure the hero image with ",[194,873,867],{},[194,875,876],{},"fetchpriority=\"high\"",", keep fonts to a small set, and move analytics and widgets behind triggers so they do not compete with hydration.",[150,879,881],{"id":880},"client-side-navigation-in-nuxt","Client-Side Navigation in Nuxt",[15,883,884,885,888,889,891,892,895,896,899,900,903,904,906],{},"After the first load, Nuxt navigates client-side. Route components are code-split and prefetched when ",[194,886,887],{},"NuxtLink"," elements are visible, and data for the next page is fetched by its ",[194,890,334],{}," calls during navigation. Slow navigations usually come from data waterfalls (sequential ",[194,893,894],{},"await useFetch"," calls that could run in parallel), large route components, or uncached API endpoints. Use parallel fetching (",[194,897,898],{},"Promise.all"," or multiple non-awaited composables), ",[194,901,902],{},"lazy: true"," for non-critical data so navigation completes before it loads, and cache API routes where possible. Payload extraction for prerendered routes (",[194,905,207],{},") lets navigations to static pages fetch only a small JSON file.",[150,908,910],{"id":909},"composables-plugins-and-global-cost","Composables, Plugins and Global Cost",[15,912,913,914,419,917,920],{},"Code that runs on every page deserves extra scrutiny. Nuxt plugins execute during app initialisation on both server and client, so a plugin that initialises an analytics SDK, loads a large configuration object or registers many global components adds to every page's startup time. Make plugins lazy (initialise on first use), restrict them to the client or server side as appropriate (",[194,915,916],{},".client.ts",[194,918,919],{},".server.ts","), and move rarely used functionality into composables imported only where needed. Global components registered in plugins end up in the main bundle; prefer auto-imported components, which Nuxt code-splits per page.",[15,922,923,924,927],{},"Composables that fetch data deserve the same attention. A ",[194,925,926],{},"useUser()"," composable that fetches the full user profile on every page — including pages that only need the user's name — adds to TTFB and payload everywhere. Give composables options to fetch only what the caller needs, and cache results with a shared key so repeated calls do not refetch.",[150,929,931],{"id":930},"vue-level-rendering-optimizations","Vue-Level Rendering Optimizations",[15,933,934,935,938,939,942,943,946],{},"Inside components, Vue offers a few tools for expensive templates. ",[194,936,937],{},"v-memo"," skips re-rendering a subtree when its listed dependencies have not changed, which helps large lists where most items stay the same between updates. ",[194,940,941],{},"v-once"," renders content once and never updates it, useful for static content inside dynamic components. Splitting large components into smaller ones lets Vue's reactivity update only the parts whose dependencies changed. And ",[194,944,945],{},"computed"," values cache derived data, avoiding repeated filtering or formatting in templates. These are refinements; the larger gains in most Nuxt apps come from payload, hydration and caching, but on data-heavy screens they make interactions noticeably faster.",[150,948,950],{"id":949},"measuring-nuxt-apps-in-the-field","Measuring Nuxt Apps in the Field",[15,952,953,954,957],{},"Use the ",[194,955,956],{},"web-vitals"," library in a client plugin to report LCP, INP, CLS and TTFB with the route name, and segment by route pattern in your analytics. Nuxt DevTools helps during development: it shows component render times, hydration timing, payload contents and route rendering modes. In production, compare field data before and after each change, and keep an eye on hydration-related long tasks with the Long Animation Frames API, which attributes long frames to scripts and shows whether framework startup still dominates the first seconds.",[150,959,961],{"id":960},"a-rollout-plan","A Rollout Plan",[963,964,965,968,971,974,977,980,983],"ol",{},[191,966,967],{},"Measure payload, client JS, hydration time and field metrics per route.",[191,969,970],{},"Set route rules: prerender or cache everything that can be.",[191,972,973],{},"Trim payloads with pick, transform and lean server routes.",[191,975,976],{},"Convert static sections to server components; lazy-hydrate below-the-fold interactivity.",[191,978,979],{},"Use shallowRef and markRaw for large data; virtualize long lists.",[191,981,982],{},"Configure Nuxt Image, Fonts and Scripts for the LCP image, fonts and third-party scripts, so they no longer compete with hydration.",[191,984,985],{},"Add payload and bundle budgets to CI.",[150,987,989],{"id":988},"common-pitfalls","Common Pitfalls",[188,991,992,998,1004,1010,1016],{},[191,993,994,997],{},[28,995,996],{},"Fetching full API responses."," Bloated payloads in every HTML response.",[191,999,1000,1003],{},[28,1001,1002],{},"Hydrating static sections."," Footers, SEO text and content hydrated for no reason.",[191,1005,1006,1009],{},[28,1007,1008],{},"Deep reactivity for large data."," Proxies everywhere, slow updates.",[191,1011,1012,1015],{},[28,1013,1014],{},"SSR for every route."," Cacheable pages rendered on each request.",[191,1017,1018,1021],{},[28,1019,1020],{},"Calling $fetch in setup."," Duplicate requests on server and client.",[150,1023,1025],{"id":1024},"faq","FAQ",[1027,1028,1031,1035],"details",{"className":1029},[1030],"faq-item",[1032,1033,1034],"summary",{},"What is the Nuxt payload?",[15,1036,1037,1038,335,1040,1042],{},"Serialised data embedded in the HTML so the client can hydrate without refetching. It includes everything fetched with ",[194,1039,334],{},[194,1041,338],{}," during server rendering.",[1027,1044,1046,1049],{"className":1045},[1030],[1032,1047,1048],{},"How do I reduce payload size?",[15,1050,323,1051,327,1053,1055],{},[194,1052,326],{},[194,1054,330],{}," to keep only needed fields, return lean data from server routes, and avoid fetching data that the page does not render.",[1027,1057,1059,1062],{"className":1058},[1030],[1032,1060,1061],{},"Does Nuxt support lazy hydration?",[15,1063,1064],{},"Yes. Lazy components can hydrate on visibility, idle time, interaction and other triggers, and server components avoid hydration entirely.",[1027,1066,1068,1071],{"className":1067},[1030],[1032,1069,1070],{},"When should I use shallowRef?",[15,1072,1073],{},"For large objects or arrays that are replaced rather than mutated, and for data you do not need deep reactivity on.",[1027,1075,1077,1080],{"className":1076},[1030],[1032,1078,1079],{},"What are route rules?",[15,1081,1082],{},"Per-route configuration for rendering and caching: prerender, SWR\u002FISR caching, client-only rendering, headers and redirects.",[1027,1084,1086,1089],{"className":1085},[1030],[1032,1087,1088],{},"Is Vue faster than React?",[15,1090,1091],{},"Vue's fine-grained reactivity often does less work per update by default. Real-world performance depends more on payloads, hydration and architecture than on the framework.",[1027,1093,1095,1098],{"className":1094},[1030],[1032,1096,1097],{},"Does Nuxt prefetch routes?",[15,1099,1100],{},"Yes. NuxtLink prefetches route components for visible links. Data is fetched during navigation unless you prefetch it explicitly.",[1027,1102,1104,1107],{"className":1103},[1030],[1032,1105,1106],{},"Does Nuxt Image improve LCP?",[15,1108,1109,1110,1112,1113,1115],{},"Yes, when configured: responsive sizes, modern formats and ",[194,1111,867],{}," with ",[194,1114,876],{}," on the hero image. Without priority, the LCP image may still be discovered late.",[1027,1117,1119,1122],{"className":1118},[1030],[1032,1120,1121],{},"Should I use Nuxt server components?",[15,1123,1124],{},"For static sections that never need interactivity, yes. They ship no client JavaScript and do not hydrate.",[1027,1126,1128,1131],{"className":1127},[1030],[1032,1129,1130],{},"Is client-side rendering ever right in Nuxt?",[15,1132,1133,1134,1136],{},"For private, highly interactive areas without SEO needs — dashboards and admin tools — ",[194,1135,722],{}," can be appropriate. Public pages should be server-rendered or prerendered.",[1027,1138,1140,1143],{"className":1139},[1030],[1032,1141,1142],{},"How do I find slow components in Nuxt?",[15,1144,1145],{},"Use Nuxt DevTools and Vue DevTools to see render timings, then confirm with a throttled Performance panel trace of the interaction.",[1027,1147,1149,1152],{"className":1148},[1030],[1032,1150,1151],{},"Does Nuxt support streaming?",[15,1153,1154],{},"Nuxt renders server output progressively with appropriate configuration; check that proxies and the hosting platform pass streamed responses through.",[150,1156,1158],{"id":1157},"guides-in-this-topic","Guides in This Topic",[188,1160,1161,1168,1175,1182],{},[191,1162,1163,1167],{},[19,1164,1166],{"href":1165},"\u002Fframework-performance\u002Fvue-and-nuxt-performance\u002Freducing-nuxt-payload-size\u002F","Reducing Nuxt payload size"," — serialise only what you render.",[191,1169,1170,1174],{},[19,1171,1173],{"href":1172},"\u002Fframework-performance\u002Fvue-and-nuxt-performance\u002Fcutting-vue-reactivity-cost-with-shallowref\u002F","Cutting Vue reactivity cost with shallowRef"," — cheaper state for large data.",[191,1176,1177,1181],{},[19,1178,1180],{"href":1179},"\u002Fframework-performance\u002Fvue-and-nuxt-performance\u002Flazy-hydration-in-nuxt\u002F","Lazy hydration in Nuxt"," — hydrate later or not at all.",[191,1183,1184,1188],{},[19,1185,1187],{"href":1186},"\u002Fframework-performance\u002Fvue-and-nuxt-performance\u002Fnuxt-route-rules-for-hybrid-rendering\u002F","Nuxt route rules for hybrid rendering"," — the right rendering mode per route.",[150,1190,1192],{"id":1191},"related","Related",[188,1194,1195,1201,1208],{},[191,1196,1197,1200],{},[19,1198,1199],{"href":21},"Framework performance"," — the section overview.",[191,1202,1203,1207],{},[19,1204,1206],{"href":1205},"\u002Fnetwork-protocol-optimization\u002Ftime-to-first-byte-optimization\u002F","Time to First Byte (TTFB) optimization"," — server response time for SSR routes.",[191,1209,1210,1214],{},[19,1211,1213],{"href":1212},"\u002Frendering-css-performance\u002Fdom-size-and-list-virtualization\u002Fvirtualizing-long-lists-in-react\u002F","Virtualizing long lists in React"," — the same technique applies in Vue.",[1216,1217,1219],"script",{"type":1218},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"Vue & Nuxt Performance: Fast Pages With Less Hydration\",\n  \"description\": \"How to make Vue and Nuxt applications fast — rendering modes, payload size, hydration, reactivity overhead and data fetching — using the framework's built-in tools.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Environment Setup: Inspect Payload and Bundles\",\n      \"text\": \"Environment Setup: Inspect Payload and Bundles\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Capture a Baseline\",\n      \"text\": \"Record HTML and payload size, client JavaScript per route, hydration time in a throttled trace, and field LCP\u002FINP per route.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Isolate Causes\",\n      \"text\": \"Large payload → data fetching returns whole API responses.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Apply Nuxt-Native Fixes\",\n      \"text\": \"Use pick or transform in useFetch\u002FuseAsyncData to keep only needed fields.\"\n    }\n  ]\n}\n",[1216,1221,1222],{"type":1218},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"Vue & Nuxt Performance: Fast Pages With Less Hydration\",\n  \"description\": \"How to make Vue and Nuxt applications fast — rendering modes, payload size, hydration, reactivity overhead and data fetching — using the framework's built-in tools.\",\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\u002Fframework-performance\u002Fvue-and-nuxt-performance\u002F\"\n  }\n}\n",[1216,1224,1225],{"type":1218},"\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\": \"Framework Performance\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fframework-performance\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 3,\n      \"name\": \"Vue & Nuxt Performance\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fframework-performance\u002Fvue-and-nuxt-performance\u002F\"\n    }\n  ]\n}\n",[1227,1228,1229],"style",{},"html pre.shiki code .sjfSM, html code.shiki .sjfSM{--shiki-default:#66707B;--shiki-dark:#BDC4CC;--shiki-light:#66707B}html pre.shiki code .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}html pre.shiki code .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}html pre.shiki code .sPARh, html code.shiki .sPARh{--shiki-default:#A0111F;--shiki-dark:#FF9492;--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 .saISM, html code.shiki .saISM{--shiki-default:#0E1116;--shiki-dark:#F0F3F6;--shiki-light:#0E1116}html pre.shiki code .smZ65, html code.shiki .smZ65{--shiki-default:#622CBC;--shiki-dark:#DBB7FF;--shiki-light:#622CBC}",{"title":225,"searchDepth":239,"depth":239,"links":1231},[1232,1233,1234,1235,1236,1237,1238,1239,1240,1241,1242,1243,1244,1245,1246,1247,1248,1249,1250,1251,1252,1253],{"id":152,"depth":239,"text":153},{"id":185,"depth":239,"text":186},{"id":217,"depth":239,"text":218},{"id":305,"depth":239,"text":306},{"id":312,"depth":239,"text":313},{"id":319,"depth":239,"text":320},{"id":522,"depth":239,"text":523},{"id":564,"depth":239,"text":565},{"id":605,"depth":239,"text":606},{"id":664,"depth":239,"text":665},{"id":677,"depth":239,"text":678},{"id":701,"depth":239,"text":702},{"id":853,"depth":239,"text":854},{"id":880,"depth":239,"text":881},{"id":909,"depth":239,"text":910},{"id":930,"depth":239,"text":931},{"id":949,"depth":239,"text":950},{"id":960,"depth":239,"text":961},{"id":988,"depth":239,"text":989},{"id":1024,"depth":239,"text":1025},{"id":1157,"depth":239,"text":1158},{"id":1191,"depth":239,"text":1192},"How to make Vue and Nuxt applications fast — rendering modes, payload size, hydration, reactivity overhead and data fetching — using the framework's built-in tools.","md",{"slug":1257,"type":1258,"breadcrumb":1259,"datePublished":1265,"dateModified":1265},"vue-and-nuxt-performance","topic",[1260,1262,1263],{"name":1261,"url":335},"Home",{"name":22,"url":21},{"name":5,"url":1264},"\u002Fframework-performance\u002Fvue-and-nuxt-performance\u002F","2026-10-06",true,"\u002Fframework-performance\u002Fvue-and-nuxt-performance",{"title":1269,"description":1270},"Vue and Nuxt Performance: Payload, Hydration, INP","Speed up Vue and Nuxt apps: shrink the Nuxt payload, cut reactivity cost with shallowRef, lazy-hydrate components, and choose rendering per route with route rules.","framework-performance\u002Fvue-and-nuxt-performance\u002Findex","qNGX1aFGS8dHqXQ01m9FiuzkOA8UJWnICMXgCbRhtLE",[1274,1278],{"title":1275,"path":1276,"stem":1277,"children":-1},"When useMemo and useCallback Actually Help","\u002Fframework-performance\u002Freact-rendering-performance\u002Fwhen-usememo-and-usecallback-actually-help","framework-performance\u002Freact-rendering-performance\u002Fwhen-usememo-and-usecallback-actually-help\u002Findex",{"title":1279,"path":1280,"stem":1281,"children":-1},"Cutting Vue Reactivity Cost with shallowRef","\u002Fframework-performance\u002Fvue-and-nuxt-performance\u002Fcutting-vue-reactivity-cost-with-shallowref","framework-performance\u002Fvue-and-nuxt-performance\u002Fcutting-vue-reactivity-cost-with-shallowref\u002Findex",1791308070283]