Nuxt 3 Route Rules: Choosing the Right Rendering Strategy Per Route
Nuxt 3 Route Rules let you assign SSR, SSG, ISR, or SPA per route pattern in nuxt.config.ts. This post shows how to map each route to its optimal strategy, what the config does at build and runtime, and where platform dependencies create sharp edges.
04 Aug 2025, 15:53 UTC

The Problem: One Rendering Mode Doesn't Fit All
You're building a Nuxt 3 application with a marketing homepage, a blog, a dashboard, and an API layer. The homepage needs fast static delivery, the blog benefits from periodic revalidation, the dashboard requires user-specific SSR, and the API routes must stay dynamic. Before Nuxt 3's Route Rules, you'd pick a global rendering mode—SSR, SSG, or SPA—and fight the mismatches with workarounds.
Route Rules solve this by letting you declare the rendering strategy per route pattern in nuxt.config.ts. The configuration lives at the Nitro layer, so the decision happens before your component code runs. This post walks through how to map each route to its optimal strategy, what the config actually does at build and runtime, and where the edges get sharp.
How Route Rules Work Under the Hood
When you add routeRules to nuxt.config.ts, Nitro reads them during the build and generates a route manifest (.output/server/routes.json). At request time, the Nitro server matches the incoming path against the patterns—exact matches first, then named parameters, wildcards, and catch-all—and applies the first matching rule. Later entries in the config override earlier ones for the same pattern.
The supported keys map directly to rendering behaviors:
ssr: true(default) — render on every requestprerender: true— generate static HTML at build time vianuxi generateswr: <seconds>— serve stale cached HTML while revalidating in the background (requires edge cache)isr: { expiration: <seconds> }— incremental static regeneration with a time-to-live (requires platform support)headers,redirect,cors— declarative HTTP controls that replace middleware for common cases
Routes not matched by any rule fall back to SSR unless you set a global default.
Worked Example: A Mixed-Mode Site
Consider this nuxt.config.ts snippet:
export default defineNuxtConfig({
routeRules: {
'/': { prerender: true },
'/blog/**': { isr: { expiration: 3600 } },
'/dashboard/**': { ssr: true },
'/api/**': { cors: true, headers: { 'cache-control': 'no-store' } },
'/static/**': { prerender: true, headers: { 'cache-control': 'public, max-age=31536000, immutable' } }
}
})
Here's what each rule achieves:
| Pattern | Rule | Build Output | Runtime Behavior |
|---|---|---|---|
/ | prerender: true | HTML file in dist/ after nuxi generate | Served statically, no server invocation |
/blog/** | isr: { expiration: 3600 } | No HTML at build; route manifest entry | First request renders and caches; subsequent requests within 1 hour serve cached HTML while background revalidation runs |
/dashboard/** | ssr: true | Server handler in .output/server/ | Fresh render per request with user context |
/api/** | cors: true + headers | Server handler with CORS preflight | Dynamic execution, no caching |
/static/** | prerender: true + long cache headers | Static assets in dist/ | Immutable cache, ideal for versioned assets |
Run npm run build and inspect .output/server/routes.json to verify the manifest entries. Deploy to a target that supports ISR (Vercel, Netlify, Cloudflare Pages) and use curl -I to confirm headers like x-nitro-cached or cache-control match your rules.
Trade-offs and Sharp Edges
Route Rules are declarative, but the runtime behavior depends heavily on your deployment platform:
- ISR/SWR require platform support. On a plain Node server without an edge cache,
swrandisrfall back to SSR on every request. Check your provider's documentation before relying on revalidation timing. - Prerendering dynamic routes can explode build time. A pattern like
/product/[id]with thousands of IDs will attempt to generate all of them. Limit withnitro.prerender.routesor setpayloadExtraction: falseif you only need the shell. - No hot-swap for rendering mode. Changing a route from SSR to prerender (or vice versa) requires a full rebuild and redeploy.
- Header precedence is non-obvious. Headers from
routeRulesmerge with Nitro plugins and middleware; the final value depends on internal ordering. Test withcurl -Irather than assuming override behavior. - API routes under
server/are unaffected. Route Rules only apply to page routes. Server handlers inserver/routes/can be covered, butserver/api/andserver/middleware/run as standard Nitro handlers.
Verification Checklist Before You Deploy
- Run
npx tsc --noEmit— TypeScript validatesrouteRuleskeys via@nuxt/schemaand catches typos likeisr: 'invalid'. - Execute
npx nuxi generateand verifydist/contains HTML only forprerender: trueroutes. - Deploy to a staging environment with ISR support. Use
curl -Ion each route pattern and confirm:- Prerendered routes return
x-nitro-prerendered: true - ISR routes show
cache-controlwith your expiration - SSR routes have no caching headers unless you added them
- Prerendered routes return
- Test rule precedence: define overlapping patterns (
'/api/*': { cors: true }and'/api/users': { cors: false }) and verify the specific rule wins via response headers.
Closing: Start Small, Verify Early
Route Rules move rendering decisions from code to config, which is a win for maintainability—provided you respect the platform constraints. Pick one route (the homepage is a safe start), set prerender: true, run the verification steps, and expand from there. The manifest in .output/server/routes.json is your source of truth: if a rule isn't there, it isn't active.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.