Nuxt 4 Patterns
Use when building or debugging Nuxt 4 apps with SSR, hybrid rendering, route rules, or page-level data fetching.
When to Activate
- Hydration mismatches between server HTML and client state
- Route-level rendering decisions such as prerender, SWR, ISR, or client-only sections
- Performance work around lazy loading, lazy hydration, or payload size
- Page or component data fetching with
useFetch, useAsyncData, or $fetch
- Nuxt routing issues tied to route params, middleware, or SSR/client differences
Hydration Safety
- Keep the first render deterministic. Do not put
Date.now(), Math.random(), browser-only APIs, or storage reads directly into SSR-rendered template state.
- Move browser-only logic behind
onMounted(), import.meta.client, ClientOnly, or a .client.vue component when the server cannot produce the same markup.
- Use Nuxt's
useRoute() composable, not the one from vue-router.
- Do not use
route.fullPath to drive SSR-rendered markup. URL fragments are client-only, which can create hydration mismatches.
- Treat
ssr: false as an escape hatch for truly browser-only areas, not a default fix for mismatches.
Data Fetching
- Prefer
await useFetch() for SSR-safe API reads in pages and components. It forwards server-fetched data into the Nuxt payload and avoids a second fetch on hydration.
- Use
useAsyncData() when the fetcher is not a simple $fetch() call, when you need a custom key, or when you are composing multiple async sources.
- Give
useAsyncData() a stable key for cache reuse and predictable refresh behavior.
- Keep
useAsyncData() handlers side-effect free. They can run during SSR and hydration.
- Use
$fetch() for user-triggered writes or client-only actions, not top-level page data that should be hydrated from SSR.
- Use
lazy: true, useLazyFetch(), or useLazyAsyncData() for non-critical data that should not block navigation. Handle status === 'pending' in the UI.
- Use
server: false only for data that is not needed for SEO or the first paint.
- Trim payload size with
pick and prefer shallower payloads when deep reactivity is unnecessary.
const route = useRoute()
const { data: article, status, error, refresh } = await useAsyncData(
() => `article:${route.params.slug}`,
() => $fetch(`/api/articles/${route.params.slug}`),
)
const { data: comments } = await useFetch(`/api/articles/${route.params.slug}/comments`, {
lazy: true,
server: false,
})
Route Rules
Prefer routeRules in nuxt.config.ts for rendering and caching strategy:
export default defineNuxtConfig({
routeRules: {
'/': { prerender: true },
'/products/**': { swr: 3600 },
'/blog/**': { isr: true },
'/admin/**': { ssr: false },
'/api/**': { cache: { maxAge: 60 * 60 } },
},
})
prerender: static HTML at build time
swr: serve cached content and revalidate in the background
isr: incremental static regeneration on supported platforms
ssr: false: client-rendered route
cache or redirect: Nitro-level response behavior
Pick route rules per route group, not globally. Marketing pages, catalogs, dashboards, and APIs usually need different strategies.
Lazy Loading and Performance
- Nuxt already code-splits pages by route. Keep route boundaries meaningful before micro-optimizing component splits.
- Use the
Lazy prefix to dynamically import non-critical components.
- Conditionally render lazy components with
v-if so the chunk is not loaded until the UI actually needs it.
- Use lazy hydration for below-the-fold or non-critical interactive UI.
<template>
<LazyRecommendations v-if="showRecommendations" />
<LazyProductGallery hydrate-on-visible />
</template>
- For custom strategies, use
defineLazyHydrationComponent() with a visibility or idle strategy.
- Nuxt lazy hydration works on single-file components. Passing new props to a lazily hydrated component will trigger hydration immediately.
- Use
NuxtLink for internal navigation so Nuxt can prefetch route components and generated payloads.
Review Checklist
- First SSR render and hydrated client render produce the same markup
- Page data uses
useFetch or useAsyncData, not top-level $fetch
- Non-critical data is lazy and has explicit loading UI
- Route rules match the page's SEO and freshness requirements
- Heavy interactive islands are lazy-loaded or lazily hydrated
1---2name: nuxt4-patterns-23description: Nuxt 4 app patterns for hydration safety, performance, route rules, lazy loading, and SSR-safe data fetching with useFetch and useAsyncData.4---5
6# Nuxt 4 Patterns
7
8Use when building or debugging Nuxt 4 apps with SSR, hybrid rendering, route rules, or page-level data fetching.
9
10## When to Activate
11
12- Hydration mismatches between server HTML and client state
13- Route-level rendering decisions such as prerender, SWR, ISR, or client-only sections
14- Performance work around lazy loading, lazy hydration, or payload size
15- Page or component data fetching with `useFetch`, `useAsyncData`, or `$fetch`
16- Nuxt routing issues tied to route params, middleware, or SSR/client differences
17
18## Hydration Safety
19
20- Keep the first render deterministic. Do not put `Date.now()`, `Math.random()`, browser-only APIs, or storage reads directly into SSR-rendered template state.
21- Move browser-only logic behind `onMounted()`, `import.meta.client`, `ClientOnly`, or a `.client.vue` component when the server cannot produce the same markup.
22- Use Nuxt's `useRoute()` composable, not the one from `vue-router`.
23- Do not use `route.fullPath` to drive SSR-rendered markup. URL fragments are client-only, which can create hydration mismatches.
24- Treat `ssr: false` as an escape hatch for truly browser-only areas, not a default fix for mismatches.
25
26## Data Fetching
27
28- Prefer `await useFetch()` for SSR-safe API reads in pages and components. It forwards server-fetched data into the Nuxt payload and avoids a second fetch on hydration.
29- Use `useAsyncData()` when the fetcher is not a simple `$fetch()` call, when you need a custom key, or when you are composing multiple async sources.
30- Give `useAsyncData()` a stable key for cache reuse and predictable refresh behavior.
31- Keep `useAsyncData()` handlers side-effect free. They can run during SSR and hydration.
32- Use `$fetch()` for user-triggered writes or client-only actions, not top-level page data that should be hydrated from SSR.
33- Use `lazy: true`, `useLazyFetch()`, or `useLazyAsyncData()` for non-critical data that should not block navigation. Handle `status === 'pending'` in the UI.
34- Use `server: false` only for data that is not needed for SEO or the first paint.
35- Trim payload size with `pick` and prefer shallower payloads when deep reactivity is unnecessary.
36
37```ts
38const route = useRoute()
39
40const { data: article, status, error, refresh } = await useAsyncData(
41 () => `article:${route.params.slug}`,
42 () => $fetch(`/api/articles/${route.params.slug}`),
43)
44
45const { data: comments } = await useFetch(`/api/articles/${route.params.slug}/comments`, {
46 lazy: true,
47 server: false,
48})
49```
50
51## Route Rules
52
53Prefer `routeRules` in `nuxt.config.ts` for rendering and caching strategy:
54
55```ts
56export default defineNuxtConfig({
57 routeRules: {
58 '/': { prerender: true },
59 '/products/**': { swr: 3600 },
60 '/blog/**': { isr: true },
61 '/admin/**': { ssr: false },
62 '/api/**': { cache: { maxAge: 60 * 60 } },
63 },
64})
65```
66
67- `prerender`: static HTML at build time
68- `swr`: serve cached content and revalidate in the background
69- `isr`: incremental static regeneration on supported platforms
70- `ssr: false`: client-rendered route
71- `cache` or `redirect`: Nitro-level response behavior
72
73Pick route rules per route group, not globally. Marketing pages, catalogs, dashboards, and APIs usually need different strategies.
74
75## Lazy Loading and Performance
76
77- Nuxt already code-splits pages by route. Keep route boundaries meaningful before micro-optimizing component splits.
78- Use the `Lazy` prefix to dynamically import non-critical components.
79- Conditionally render lazy components with `v-if` so the chunk is not loaded until the UI actually needs it.
80- Use lazy hydration for below-the-fold or non-critical interactive UI.
81
82```vue
83<template>
84 <LazyRecommendations v-if="showRecommendations" />
85 <LazyProductGallery hydrate-on-visible />
86</template>
87```
88
89- For custom strategies, use `defineLazyHydrationComponent()` with a visibility or idle strategy.
90- Nuxt lazy hydration works on single-file components. Passing new props to a lazily hydrated component will trigger hydration immediately.
91- Use `NuxtLink` for internal navigation so Nuxt can prefetch route components and generated payloads.
92
93## Review Checklist
94
95- First SSR render and hydrated client render produce the same markup
96- Page data uses `useFetch` or `useAsyncData`, not top-level `$fetch`
97- Non-critical data is lazy and has explicit loading UI
98- Route rules match the page's SEO and freshness requirements
99- Heavy interactive islands are lazy-loaded or lazily hydrated