Nuxt 3 Route Rules: Mixing SSR, SSG, and ISR Without the Headache
Nuxt 3's routeRules let you assign SSR, static, SPA, or ISR rendering per route pattern — no more all-or-nothing trade-offs. A practical config example shows how to mix modes for a real site, plus platform gotchas and verification steps.
22 Aug 2026, 23:46 UTC

The Problem: One Rendering Mode Doesn't Fit All Pages
You're building a Nuxt 3 site with a marketing homepage, a blog with 500 posts, a dashboard behind auth, and a real-time pricing calculator. The homepage and blog posts rarely change — they're perfect candidates for static generation. The dashboard needs user-specific data on every request. The pricing calculator hits an external API with rate limits, so you want cached responses that refresh periodically. Before Nuxt 3, you'd pick one rendering strategy for the entire app and fight the mismatches with workarounds.
Route Rules: Per-Page Rendering Decisions
Nuxt 3's routeRules in nuxt.config.ts lets you assign a rendering mode to any route pattern. The four built-in modes cover most needs:
- ssr (default): Server-side render on every request
- static: Pre-render at build time via
nuxt generate - spa: Client-only hydration, no server render
- swr/isr: Stale-while-revalidate — serve stale static payload, revalidate in background after TTL
Rules cascade: more specific patterns override broader ones. You can also attach headers, redirects, and CORS per rule, giving you cache-control without writing middleware.
Worked Example: A Mixed-Mode Site
Here's a practical nuxt.config.ts for the scenario above:
export default defineNuxtConfig({
routeRules: {
// Marketing pages — static at build
'/': { static: true },
'/about': { static: true },
'/features': { static: true },
// Blog — static for known posts, ISR for the rest
'/blog/**': { swr: 3600 }, // 1-hour TTL
// Dashboard — always SSR, no caching
'/dashboard/**': { ssr: true },
// Pricing calculator — ISR with longer TTL
'/pricing': { isr: 86400 }, // 24-hour TTL
// Admin — SPA mode, client-only
'/admin/**': { spa: true },
// API routes are NOT covered by routeRules
// Configure caching via Nitro storage or response headers instead
},
// Explicitly list dynamic blog routes for prerendering
nitro: {
prerender: {
routes: [
'/blog/getting-started',
'/blog/advanced-patterns',
'/blog/migration-guide'
],
crawlLinks: true // discover linked routes automatically
}
}
})
Run npm run generate and inspect .output/public — you'll see index.html, about/index.html, features/index.html, and the three explicit blog posts as static files. The remaining /blog/** routes and /pricing get an initial static payload at build, then revalidate on-demand after their TTL expires. The dashboard and admin routes produce no static output.
What the Composables See
useFetch and useAsyncData respect route rules automatically. On a static or ISR route, the first load returns the prerendered payload from the HTML (no extra network request). Client-side navigation then hydrates and fetches fresh data if the TTL expired. On SSR routes, every navigation triggers a server request. On SPA routes, the composables run entirely in the browser — useful for auth-gated pages where you want to avoid SSR entirely.
Trade-offs and Platform Gotchas
- ISR/SWR behavior varies by deployment target. Vercel, Netlify, Cloudflare Workers, and Nitro on Node each implement background revalidation differently. Test on your actual platform — a route that revalidates cleanly on Vercel might serve stale data indefinitely on Cloudflare Pages without additional configuration.
- Prerendering dynamic routes can explode build time.
crawlLinks: truefollows every<NuxtLink>; a blog with 5,000 posts will prerender all of them. Prefer a sitemap-driven route list or limit crawling to high-traffic paths. - Route rules don't apply to
server/api/routes. Those are always SSR. Cache them explicitly withsetResponseHeadersor Nitro storage. - Switching a route's mode requires a rebuild. There's no zero-downtime toggle for a single route. Plan your rendering strategy before deploying.
- SPA routes skip server middleware. Auth guards in
server/middleware/won't run for client-side navigation to an SPA route. Duplicate the check in a client-side plugin or composable.
Verify Before You Ship
- Create a minimal project:
npx nuxi@latest init test-app - Add the route rules above to
nuxt.config.ts - Run
npm run generateand confirm.output/publiccontains only the expected static files - Run
npx nuxi build --analyze— SPA routes should not appear in the server bundle - Deploy to your target platform (Vercel, Netlify,
nitro: cloudflare) and hit an ISR/SWR route after TTL expiry; check response headers forx-nitro-prerenderedand cache timing
Closing: Pick the Right Tool per Page
Hybrid rendering isn't about using every mode — it's about matching each page's freshness requirements to the cheapest rendering strategy that satisfies them. Static for marketing, ISR for semi-dynamic content, SSR for personalized data, SPA for client-only apps. Route rules make this explicit in config, not scattered across middleware and build scripts. Start with the simplest mode that works, add ISR where you need periodic freshness, and keep SSR only where you truly need per-request data. Your build times and infrastructure costs will reflect the difference.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.