Vue 3 and Nuxt · Building Apps with Nuxt
Rendering Modes and Route Rules
Choose rendering per route with routeRules: prerendering, stale-while-revalidate caching, client-only pages, redirects and headers.
What you'll build
One Nuxt app with different rendering per route: a prerendered home page, blog posts cached with stale-while-revalidate, a client-only account area, a redirect from old URLs, and no-store headers on the API. Server logs show which requests actually render.
The idea
- Universal rendering (SSR + hydration) is the default: every request renders on the server.
routeRulesinnuxt.config.tschange this per route pattern:prerender: truerenders at build time into static HTML;swr: secondscaches the rendered response and refreshes it in the background;isris the CDN-level equivalent on supported hosts;ssr: falsesends an app shell and renders in the browser (fine for private dashboards, bad for SEO);redirectandheadersapply without rendering.
The code
// file: nuxt.config.ts
export default defineNuxtConfig({
compatibilityDate: "2026-10-01",
devtools: { enabled: false },
routeRules: {
"/": { prerender: true },
"/blog/**": { swr: 3600 },
"/account/**": { ssr: false },
"/old-blog/**": { redirect: { to: "/blog/**", statusCode: 301 } },
"/api/**": { headers: { "cache-control": "no-store" } },
},
});
// file: app/app.vue
<template><main><NuxtPage /></main></template>
// file: app/pages/index.vue
<script setup lang="ts">
console.log("[app] rendering home");
</script>
<template><h1>Welcome</h1></template>
// file: app/pages/blog/[slug].vue
<script setup lang="ts">
const slug = useRoute().params.slug;
console.log(`[app] rendering blog post ${slug}`);
</script>
<template><h1>Post: {{ slug }}</h1></template>
// file: app/pages/account/index.vue
<template><h1>Your account</h1></template>
// file: server/api/time.get.ts
export default defineEventHandler(() => ({ now: "fresh on every request" }));
// headers: cache-control, x-nitro-prerender
// request: GET /
// request: GET /blog/hello
// request: GET /blog/hello
// request: GET /account find:"Your account"
// request: GET /old-blog/hello
// request: GET /api/timeOutput
GET / -> 200
Welcome
GET /blog/hello -> 200 (cache-control: s-maxage=3600, stale-while-revalidate)
[app] rendering blog post hello
Post: hello
GET /blog/hello -> 200 (cache-control: s-maxage=3600, stale-while-revalidate)
Post: hello
GET /account -> 200
response contains "Your account": no
GET /old-blog/hello -> 301 (Location: /blog/hello)
GET /api/time -> 200 (cache-control: no-store)
{"now": "fresh on every request"}Run this on your computer — see “Get Started” for setup.
The home page rendered at build time, so no request logs "rendering home". The blog post rendered once; the second request came from the SWR cache. The account page HTML doesn't contain its heading: it renders in the browser. The redirect and the API header applied without rendering anything.
Common mistakes
- Prerendering pages with per-user content; everyone gets the same HTML.
ssr: falseon public pages that need search traffic.- SWR-caching responses that depend on cookies (per-user data cached for everyone).
- Running several server instances with the default in-memory cache and expecting shared caching; configure Nitro storage (Redis) for the cache.
Key points
- SSR by default; routeRules choose per route.
- prerender for static pages, swr/isr for cacheable dynamic pages, ssr: false for private app areas.
- Redirects and headers can be route rules too.
Further reading: Rendering modes · Hybrid rendering · Nitro cache storage
Practice questions
What does swr in Nuxt route rules do?
Answer
It caches the rendered response on the server for the given time and serves it, regenerating it in the background when it becomes stale.
When is ssr: false appropriate?
Answer
For private, highly interactive areas like dashboards that don't need SEO; the server sends an app shell and the browser renders the page.
Why shouldn't you prerender or SWR-cache per-user pages?
Answer
The cached HTML is shared by all visitors, so one user's content would be shown to others.
Open in the app to mark lessons complete, take notes and track your progress.