nuxt
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 routeRules. Assign rendering strategies per route rather than using a single global mode:
// 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:
prerender: true— generate at build time, serve as static assetswr: N— server/proxy cache for N seconds, stale-while-revalidateisr: N— CDN cache until next deploy (Vercel/Netlify);isr: truemeans persist indefinitelyssr: false— browser-only rendering (SPA mode for that route)redirect: '/new'— server-side redirectheaders: {}— 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 references/rendering.md for a full breakdown of trade-offs and deployment targets.
Component Best Practices
Lazy-Load Non-Critical Components
Prefix any component with Lazy to defer its JavaScript until needed. This directly reduces initial bundle size and improves Time to Interactive (TTI):
<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:
<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 useFetch and useAsyncData
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:
// 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 await outside of <script setup>, defineNuxtComponent, defineNuxtPlugin, or defineNuxtRouteMiddleware:
// 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 <NuxtImg>
Replace all <img> tags with <NuxtImg> (requires @nuxt/image). It auto-converts to WebP/Avif, resizes, and generates responsive sizes:
<!-- 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 width and height to prevent layout shift (CLS).
Fonts: Use Nuxt Fonts
Add @nuxt/fonts to automatically self-host fonts, inject @font-face rules, and generate fallback metrics that minimize CLS. No manual <link rel="preload"> required.
Third-Party Scripts: Use Nuxt Scripts
Add @nuxt/scripts to load analytics, embeds, and social widgets without blocking the main thread. Scripts support deferred triggers and typed proxies:
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 <NuxtLink>
<NuxtLink> automatically prefetches JavaScript for in-viewport links. To reduce bandwidth on large sites, switch to interaction-based prefetching:
// nuxt.config.ts
export default defineNuxtConfig({
experimental: {
defaults: {
nuxtLink: { prefetchOn: 'interaction' },
},
},
})
Apply Vue-Level Optimizations
Nuxt apps are Vue apps — apply Vue performance primitives:
shallowRef/shallowReactivefor large objects not needing deep reactivityv-oncefor static subtrees that never changev-memoto skip re-renders when dependencies are unchangedcomputedto cache derived state rather than recalculating in templates
Auto-Imports
Nuxt auto-imports components from app/components/, composables from app/composables/, and utilities from app/utils/. Only used exports appear in the production bundle — no manual tree-shaking needed.
Place server-only utilities in server/utils/ — they are also auto-imported within the server/ directory.
To make imports explicit (useful for clarity or monorepos), use the #imports alias:
import { ref, computed, useFetch } from '#imports'
Common Pitfalls
| Problem | Solution |
|---|---|
| Double data fetch (server + client) | Use useFetch / useAsyncData instead of raw $fetch |
| Plugins blocking hydration | Move non-global logic to composables |
| Large bundles | Run nuxi analyze; lazy-load large components |
| Layout shift from images | Always set width/height on <NuxtImg> |
| Unoptimized fonts | Add @nuxt/fonts |
| INP degradation from third-party scripts | Add @nuxt/scripts with deferred triggers |
| Deep reactivity on large objects | Use shallowRef / shallowReactive |
Composable called after await |
Call composables synchronously before async operations |
| Unused dependencies bloating bundle | Audit package.json; remove unused packages |
Profiling Workflow
- Bundle analysis —
npx nuxi analyzegenerates 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,routeRulesoptions, and Core Web Vitals targetsreferences/rendering.md— In-depth rendering mode trade-offs, hydration mismatch prevention, and deployment targets
More from the-perfect-developer/the-perfect-opencode
turso-libsql
This skill should be used when the user asks to "connect to Turso", "use libSQL", "set up a Turso database", "query Turso with TypeScript", or needs guidance on Turso Cloud, embedded replicas, or vector search with libSQL.
11alpinejs
This skill should be used when the user asks to "add Alpine.js", "create Alpine component", "use Alpine directives", "build interactive UI with Alpine", or needs guidance on Alpine.js development patterns and best practices.
10python-dependency-injection
This skill should be used when the user asks to "implement dependency injection in Python", "use the dependency-injector library", "decouple Python components", "write testable Python services", or needs guidance on Inversion of Control, DI containers, provider types, and wiring in Python applications.
3copilot-sdk
This skill should be used when the user asks to "integrate GitHub Copilot into an app", "use the Copilot SDK", "build a Copilot-powered agent", "embed Copilot in a service", or needs guidance on the GitHub Copilot SDK for Python, TypeScript, Go, or .NET.
3conventional-git-commit
This skill MUST be loaded on every git commit without exception. It should also be used when the user asks to "write a conventional commit", "format a commit message", "follow conventional commits spec", "create a semantic commit", "make a commit", "commit changes", or "git commit". Every commit message produced in this project MUST conform to this specification.
3agent-configuration
This skill should be used when the user asks to "configure agents", "create a custom agent", "set up agent permissions", "customize agent behavior", "switch agents", or needs guidance on OpenCode agent system.
3