One Nuxt App, Four Rendering Modes: Route Rules in Practice
Nuxt 3's routeRules let you pick SSR, static, SWR, or SPA rendering per route in one app. A worked e-commerce example, verification steps, and honest trade-offs.
20 Dec 2025, 12:39 UTC

Most rendering-strategy debates are framed as a fork in the road: SSR or SSG, pick one, live with it. Nuxt 3 quietly removes that fork. Through routeRules in nuxt.config.ts, a single application can serve some pages as pre-rendered static HTML, others as server-rendered, others with stale-while-revalidate caching, and others as pure client-side SPAs — all compiled into one deployable output by the Nitro server engine.
The thesis is simple: rendering mode is a per-route engineering decision, not a per-project one. Here's how to make that decision well.
What route rules actually do
Nitro, the server layer under Nuxt 3, reads your route rules at build time and compiles each matching route into the appropriate output. On a Node server, SSR routes become request handlers; static routes become plain HTML files; on edge platforms, rules map onto edge functions. The main strategies:
ssr: true— render on each request (the default).prerender: true— generate HTML at build time.swr: 60— serve a cached response and revalidate in the background after the TTL (in seconds) expires.ssr: false— client-only rendering; the server sends a minimal shell.
Patterns are matched in order, first match wins, and glob syntax like /blog/** covers whole sections. Your data-fetching composables adapt automatically: useFetch runs at build time on prerendered routes, in the background on SWR routes, and in the browser on SPA routes.
A worked example: the e-commerce split
Consider a storefront with four distinct workloads. Product pages change rarely and need SEO — pre-render them. Category listings change often but tolerate a minute of staleness — SWR. The account dashboard is personalized — SSR. The admin panel is behind auth with no SEO value — SPA.
// nuxt.config.ts (Nuxt 3.8+ recommended)
export default defineNuxtConfig({
routeRules: {
'/products/**': { prerender: true },
'/categories/**': { swr: 60 },
'/dashboard/**': { ssr: true },
'/admin/**': { ssr: false },
},
nitro: {
prerender: {
// Dynamic routes the crawler can't reach via links
routes: ['/products/1', '/products/2'], // generate from your CMS/API
},
},
})Nitro's prerender crawler discovers static pages by following links, but dynamic product URLs usually aren't all linkable, so you enumerate them explicitly — typically by fetching IDs from your CMS in the config at build time.
Verifying it actually worked
Run npm run build locally and inspect .output/: prerendered routes appear as .html files under .output/public/, while SSR routes exist only as server handlers. After deploying (Vercel, Netlify, and Cloudflare Pages all support this zero-config), check response headers in the browser's network tab — Nitro emits markers such as x-nitro-prerendered on static hits. To confirm SWR revalidation, request an SWR route, wait past the TTL, request again, and watch for a background refresh in server logs or a changed response on the following hit. Don't trust the config file alone; trust the headers.
The trade-offs worth knowing
SWR needs the right host. Stale-while-revalidate requires a platform that can revalidate in the background or a long-running server. On purely static hosting (GitHub Pages, S3), SWR silently degrades to plain static behavior — no revalidation ever happens. Verify your platform before relying on freshness.
Pre-rendering at scale costs build time. Ten thousand product pages means ten thousand render passes at build. For very large catalogs, SWR on a supported platform is often the better answer: popular pages get cached on first request instead of all pages being built up front.
Rules are per-page, not per-component. A page that needs a static shell but a personalized widget can't express that in route rules. Use <ClientOnly> or lazy hydration for the dynamic island inside an otherwise static page.
ssr: false kills SEO for that route. The initial HTML is an empty shell, so crawlers and social-card scrapers see nothing. That's fine for an admin panel, disastrous for a landing page.
Rules are build-time configuration. Changing them means rebuilding and redeploying; there's no runtime toggle without custom Nitro middleware.
Where to start
The pragmatic path is incremental: ship SSR everywhere first, then measure. Move your highest-traffic, lowest-churn routes to prerender or swr, and quarantine auth-only sections behind ssr: false. You get the performance profile of a multi-framework architecture without ejecting from one codebase — and every decision stays reversible with a config edit and a redeploy.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.