Nuxt Best Practices
Guidance for building high-performance Nuxt 4 applications following official best practices. Covers rendering strategies, performance optimization, data fetching, component patterns, and profiling.
Rendering Strategies
Choose the Right Rendering Mode per Route
Nuxt 4 supports hybrid rendering via
. Assign rendering strategies per route rather than using a single global mode:
ts
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
'/': { prerender: true }, // Static at build time
'/products/**': { swr: 3600 }, // Cached, revalidated in background
'/blog': { isr: 3600 }, // CDN-cached, revalidated hourly
'/admin/**': { ssr: false }, // Client-side only
'/api/**': { cors: true },
},
})
Route rule reference:
- — generate at build time, serve as static asset
- — server/proxy cache for N seconds, stale-while-revalidate
- — CDN cache until next deploy (Vercel/Netlify); means persist indefinitely
- — browser-only rendering (SPA mode for that route)
- — server-side redirect
- — add custom response headers (e.g., long cache on assets)
Default to Universal Rendering
Universal rendering (SSR + hydration) is the default and best choice for content-oriented apps (blogs, e-commerce, marketing sites). It delivers:
- Immediate HTML visible to users and crawlers
- Full interactivity after hydration
- Better Core Web Vitals scores (LCP, CLS)
Edge-Side Rendering
Deploy to CDN edge servers (Cloudflare Workers, Vercel Edge, Netlify Edge) for reduced latency. Nuxt's Nitro engine supports these out of the box — no code changes required, only a different build preset.
See
for a full breakdown of trade-offs and deployment targets.
Component Best Practices
Lazy-Load Non-Critical Components
Prefix any component with
to defer its JavaScript until needed. This directly reduces initial bundle size and improves Time to Interactive (TTI):
html
<script setup lang="ts">
const show = ref(false)
</script>
<template>
<div>
<button @click="show = true">Load list</button>
<LazyMountainsList v-if="show" />
</div>
</template>
Use Lazy Hydration (Nuxt 3.16+)
Defer hydration of components until they enter the viewport or the browser is idle. This improves TTI without sacrificing SSR-rendered content:
html
<template>
<!-- Hydrate only when scrolled into view -->
<LazyCommentSection hydrate-on-visible />
<!-- Hydrate when browser is idle -->
<LazyAnalyticsWidget hydrate-on-idle />
<!-- Hydrate on user interaction -->
<LazyChatWidget hydrate-on-interaction />
</template>
Avoid Overusing Plugins
Plugins execute during the hydration phase and block interactivity. Audit plugins regularly — if logic does not need to run globally at startup, move it to a composable or utility function instead.
Data Fetching
Use and
These composables deduplicate server-side fetches. Data fetched on the server is serialized into the page payload and reused by the client — no double fetch:
ts
// Good: data transferred via payload, no duplicate network request
const { data } = await useFetch('/api/products')
// Bad: runs separately on server and client
const data = await $fetch('/api/products')
Keep Composables Synchronous at the Top Level
Vue and Nuxt composables rely on a synchronous lifecycle context. Do not call composables after an
outside of
,
,
, or
defineNuxtRouteMiddleware
:
ts
// Bad
const data = await someAsyncOperation()
const config = useRuntimeConfig() // context lost
// Good — call composable before await, or inside <script setup>
const config = useRuntimeConfig()
const data = await someAsyncOperation()
Performance Optimization
Images: Use
Replace all
tags with
(requires
). It auto-converts to WebP/Avif, resizes, and generates responsive
:
html
<!-- Above-the-fold / LCP image -->
<NuxtImg
src="/hero.jpg"
format="webp"
preload
loading="eager"
fetch-priority="high"
width="1200"
height="600"
/>
<!-- Below-the-fold image -->
<NuxtImg
src="/feature.jpg"
format="webp"
loading="lazy"
fetch-priority="low"
width="600"
height="400"
/>
Always set
and
to prevent layout shift (CLS).
Fonts: Use Nuxt Fonts
Add
to automatically self-host fonts, inject
rules, and generate fallback metrics that minimize CLS. No manual
required.
Third-Party Scripts: Use Nuxt Scripts
Add
to load analytics, embeds, and social widgets without blocking the main thread. Scripts support deferred triggers and typed proxies:
ts
const { proxy } = useScriptGoogleAnalytics({
id: 'G-XXXXXXXX',
scriptOptions: { trigger: 'manual' },
})
// Safe to call before script loads — events are queued
proxy.gtag('event', 'page_view')
Leverage Smart Prefetching via
automatically prefetches JavaScript for in-viewport links. To reduce bandwidth on large sites, switch to interaction-based prefetching:
ts
// nuxt.config.ts
export default defineNuxtConfig({
experimental: {
defaults: {
nuxtLink: { prefetchOn: 'interaction' },
},
},
})
Apply Vue-Level Optimizations
Nuxt apps are Vue apps — apply Vue performance primitives:
- / for large objects not needing deep reactivity
- for static subtrees that never change
- to skip re-renders when dependencies are unchanged
- to cache derived state rather than recalculating in templates
Auto-Imports
Nuxt auto-imports components from
, composables from
, and utilities from
. Only used exports appear in the production bundle — no manual tree-shaking needed.
Place server-only utilities in
— they are also auto-imported within the
directory.
To make imports explicit (useful for clarity or monorepos), use the
alias:
ts
import { ref, computed, useFetch } from '#imports'
Common Pitfalls
| Problem | Solution |
|---|
| Double data fetch (server + client) | Use / instead of raw |
| Plugins blocking hydration | Move non-global logic to composables |
| Large bundles | Run ; lazy-load large components |
| Layout shift from images | Always set / on |
| Unoptimized fonts | Add |
| INP degradation from third-party scripts | Add with deferred triggers |
| Deep reactivity on large objects | Use / |
| Composable called after | Call composables synchronously before async operations |
| Unused dependencies bloating bundle | Audit ; remove unused packages |
Profiling Workflow
- Bundle analysis — generates a visual treemap; identify large dependencies.
- Nuxt DevTools — Timeline, Render Tree, and Inspect tabs reveal component render costs and file sizes.
- Chrome DevTools — Performance panel shows LCP/CLS live; Lighthouse gives actionable scores.
- PageSpeed Insights — Combine lab + real-world field data for production auditing.
- WebPageTest — Test from multiple global regions and network conditions.
Additional Resources
Reference Files
references/performance.md
— Detailed breakdown of all performance techniques, options, and Core Web Vitals targets
- — In-depth rendering mode trade-offs, hydration mismatch prevention, and deployment targets