Nuxt 4 can pre-render content, render pages on a server, cache selected routes, and control hydration at component level. Those capabilities are useful, but they do not make every Nuxt page fast automatically. Slow server work, oversized payloads, eager components, poorly configured images, and third-party scripts can still dominate the result.
Optimize the route users receive, verify the generated output, and compare repeated tests under the same conditions.
Measure the real route
Start with a production URL and record its state: device mode, region, cache status, authentication, consent, experiments, and third-party content. Run Lighthouse several times and compare a representative median rather than one score.
Use Chrome DevTools Performance and Network panels to identify the cause behind LCP, CLS, TBT, FCP, or Speed Index. Field Core Web Vitals describe real visits and should be evaluated separately from the lab score. How to Check Website Speed covers the full workflow.
Also capture server timing, data-source latency, cache outcomes, errors, and Nitro request duration. A late first byte and late image discovery require different fixes even if both make LCP worse.
Choose rendering and caching per route
Nuxt uses universal rendering by default. Static generation, server rendering, client-only routes, and hybrid caching each fit different requirements. The current Nuxt rendering documentation defines routeRules as a top-level Nuxt configuration option:
export default defineNuxtConfig({
routeRules: {
"/": { prerender: true },
"/blog/**": { swr: 3600 },
"/account/**": { ssr: false },
},
});
Do not nest routeRules inside nitro. Choose each rule from the route's data and privacy requirements:
- pre-render content that can be generated safely during the build;
- use SWR or supported platform ISR when shared content can be served stale while it refreshes;
- render per request when freshness, cookies, or authorization require it;
- reserve
ssr: falsefor routes that genuinely need a client-only application shell.
ssr: false can make public content appear later because the browser must execute JavaScript before rendering it. It is not a performance shortcut. Cache keys and response headers must also respect locale, account, permissions, and any other response variation.
Optimize images explicitly
The optional @nuxt/image module supplies <NuxtImg> and <NuxtPicture>. Its NuxtImg documentation states that the component can generate optimized URLs and responsive candidates when configured.
<NuxtImg
src="/images/product-dashboard.png"
alt="Product dashboard showing weekly usage trends"
width="1440"
height="900"
sizes="100vw md:1100px"
preload
/>
Use preload only for the image likely to become LCP. For appropriate off-screen images, set native lazy loading explicitly:
<NuxtImg
src="/images/customer-report.png"
alt="Customer report with retention metrics"
width="960"
height="640"
sizes="100vw md:50vw"
loading="lazy"
/>
Nuxt Image supports native lazy loading; it does not promise that every image becomes lazy automatically. Provide realistic sizes, preserve dimensions, and allow only trusted remote domains. Check that the selected provider actually supports the requested transformations and formats in the deployed environment.
If LCP remains late, inspect server response, resource discovery, download priority, and render delay. Compressing the file alone will not fix client-only discovery or a blocked main thread.
Control payload size and data fetching
useFetch and useAsyncData are SSR-aware composables. Nuxt serializes server results into the payload so hydration can reuse them instead of automatically repeating the same request. The Nuxt data-fetching guide describes this behavior.
Return only what the view needs:
<script setup lang="ts">
const route = useRoute();
const { data: product } = await useFetch(`/api/products/${route.params.slug}`, {
pick: ["name", "tagline", "logoUrl", "category"],
});
</script>
pick reduces what Nuxt serializes into the payload; it does not necessarily reduce what the upstream API sends to the server. Optimize the API query as well. Use stable explicit keys when calls should share data, and keep handlers free of side effects so SSR and hydration remain predictable.
Do not automatically block navigation on every secondary request. Nuxt supports lazy data and explicit loading states for content that can arrive later. Keep essential initial content in the server-rendered HTML when search and first-render usability depend on it.
Reduce hydration and client JavaScript
Nuxt auto-imports components, composables, utilities, and Vue APIs while preserving production tree shaking; auto-imports do not inherently bundle every available helper. Inspect the production build when a dependency is unexpectedly present.
Prefixing a component with Lazy creates a dynamic import. That alone does not always reduce initial runtime work if the component is rendered immediately. Combine code splitting with conditional rendering or a documented lazy-hydration strategy.
Nuxt 4 supports delayed hydration in single-file components:
<template>
<ProductSummary />
<LazyReviews hydrate-on-visible />
</template>
Other strategies include hydration on idle, interaction, a media query, a condition, or after a delay. The components documentation explains their constraints. Do not delay critical above-the-fold controls, and test focus, keyboard input, shared state, and the interval before the component becomes interactive.
Nuxt also supports server components, islands, and noScripts route rules for suitable content. These are architectural choices with limitations, not universal switches. Use them where the route remains complete and usable under their rules.
Load fonts and third-party scripts intentionally
Self-host fonts where licensing permits, use only required subsets and weights, and define fallback metrics carefully. Preload only a font needed for initial visible text. font-display: swap can preserve text visibility, but the replacement font may still cause movement when metrics differ.
useScript is provided by the separate Nuxt Scripts module, not Nuxt core. Install a current @nuxt/scripts release compatible with the project's Nuxt version and follow the Nuxt Scripts documentation for consent, triggers, and proxy APIs. Do not copy an old experimental snippet into core code and assume it remains supported.
For every analytics, chat, video, and advertising integration:
- load it only on routes that need it;
- respect consent before contacting the vendor;
- choose a trigger that matches when functionality is required;
- reserve visual space for embeds;
- measure network and main-thread cost after it loads.
Validate Nitro and deployment behavior
Nitro supports multiple deployment presets. A preset adapts output to a runtime; it does not guarantee a faster response in every region or topology.
nitro.compressPublicAssets can pre-compress eligible public assets at build time:
export default defineNuxtConfig({
nitro: {
compressPublicAssets: true,
},
});
Verify whether the CDN or reverse proxy already compresses responses and which variants it serves. Inspect production cache-control, content-encoding, Vary, immutable asset filenames, and HTML freshness directly. Avoid adding broad cache headers that accidentally cache personalized HTML.
Load test through the real CDN, proxy, Nitro runtime, database, and upstream services. Watch tail latency, errors, cold starts, CPU, memory, and connection capacity. Moving compute closer to visitors helps only when the data and dependencies are available with comparable latency and correctness.
Use a release checklist
Before shipping a Nuxt performance change, verify:
routeRulesare top-level and reflect real freshness and privacy needs;- initial HTML contains the essential public content;
- the LCP image is discoverable, correctly sized, and not lazy-loaded;
- off-screen images opt into appropriate lazy behavior;
- payloads contain only data needed by the client;
- lazy components and hydration strategies actually defer work;
- third-party scripts follow consent and explicit triggers;
- production compression and cache headers were inspected;
- repeated lab runs improved under equivalent conditions;
- field Core Web Vitals are monitored after deployment.
Review published measurements on IndieTools Speed, compare architecture in Next.js vs Nuxt, and use What Is a Good PageSpeed Score? to interpret the lab result without confusing it with real-user performance.


