---
title: "Rendering modes in Nuxt: SSR, SSG, ISR and routeRules · vuejs.school"
description: "Server rendering, static generation, cached pages and client-only routes, what each needs from your host, and how routeRules mixes them in one Nuxt app."
canonical_url: "https://vuejs.school/community/blog/rendering-modes-in-nuxt-ssr-ssg-isr-and-routerules"
meta:
  "og:description": "Server rendering, static generation, cached pages and client-only routes, what each needs from your host, and how routeRules mixes them in one Nuxt app."
  "og:image": "https://vuejs.school/_og/d/a_~UmVuZGVyaW5nIG1vZGVzIGluIE51eHQ6IFNTUiwgU1NHLCBJU1IgYW5kIHJvdXRlUnVsZXMgwrcgdnVlanMuc2Nob29s%2Cc_OgCard.takumi%2Ctitle_~UmVuZGVyaW5nIG1vZGVzIGluIE51eHQ6IFNTUiwgU1NHLCBJU1IgYW5kIHJvdXRlUnVsZXM%2Cdescription_~U2VydmVyIHJlbmRlcmluZywgc3RhdGljIGdlbmVyYXRpb24sIGNhY2hlZCBwYWdlcyBhbmQgY2xpZW50LW9ubHkgcm91dGVzLCB3aGF0IGVhY2ggbmVlZHMgZnJvbSB5b3VyIGhvc3QsIGFuZCBob3cgcm91dGVSdWxlcyBtaXhlcyB0aGVtIGluIG9uZSBOdXh0IGFwcC4%2Ceyebrow_~VHV0b3JpYWwgwrcgNyBtaW4gcmVhZA%2Ctint_sky%2Cp_Ii9jb21tdW5pdHkvYmxvZy9yZW5kZXJpbmctbW9kZXMtaW4tbnV4dC1zc3Itc3NnLWlzci1hbmQtcm91dGVydWxlcyI%2Cs_XjPttColquP816Ua.png"
  "og:image:alt": "Rendering modes in Nuxt: SSR, SSG, ISR and routeRules · vuejs.school"
  "og:image:height": "600"
  "og:image:type": image/png
  "og:image:width": "1200"
  "og:title": "Rendering modes in Nuxt: SSR, SSG, ISR and routeRules · vuejs.school"
  "twitter:image": "https://vuejs.school/_og/d/a_~UmVuZGVyaW5nIG1vZGVzIGluIE51eHQ6IFNTUiwgU1NHLCBJU1IgYW5kIHJvdXRlUnVsZXMgwrcgdnVlanMuc2Nob29s%2Cc_OgCard.takumi%2Ctitle_~UmVuZGVyaW5nIG1vZGVzIGluIE51eHQ6IFNTUiwgU1NHLCBJU1IgYW5kIHJvdXRlUnVsZXM%2Cdescription_~U2VydmVyIHJlbmRlcmluZywgc3RhdGljIGdlbmVyYXRpb24sIGNhY2hlZCBwYWdlcyBhbmQgY2xpZW50LW9ubHkgcm91dGVzLCB3aGF0IGVhY2ggbmVlZHMgZnJvbSB5b3VyIGhvc3QsIGFuZCBob3cgcm91dGVSdWxlcyBtaXhlcyB0aGVtIGluIG9uZSBOdXh0IGFwcC4%2Ceyebrow_~VHV0b3JpYWwgwrcgNyBtaW4gcmVhZA%2Ctint_sky%2Cp_Ii9jb21tdW5pdHkvYmxvZy9yZW5kZXJpbmctbW9kZXMtaW4tbnV4dC1zc3Itc3NnLWlzci1hbmQtcm91dGVydWxlcyI%2Cs_XjPttColquP816Ua.png"
  "twitter:image:alt": "Rendering modes in Nuxt: SSR, SSG, ISR and routeRules · vuejs.school"
---

> Index of every page on this site: [llms.txt](https://vuejs.school/llms.txt)

## Universal rendering is the default

Out of the box, Nuxt uses server-side rendering (SSR), which its docs call universal rendering. When a browser asks for a URL, Nuxt runs your Vue components on the server and sends back finished HTML. The visitor sees content straight away, and search engines can read it without running JavaScript. Then the same code runs again in the browser and attaches event listeners to that HTML. This step is called hydration, and after it the page behaves like any Vue app: later navigation happens in the browser, without a full page load.

A page that fetches data shows the split clearly:

*app/pages/products/\[id].vue*

```
<script setup lang="ts">
const route = useRoute()
const { data: product } = await useFetch(`/api/products/${route.params.id}`)
</script>

<template>
  <h1>{{ product?.name }}</h1>
  <p>{{ product?.description }}</p>
</template>
```

On the first request, `useFetch` runs on the server, the HTML arrives with the product in it, and the data travels along in the page payload so the browser does not fetch it a second time.

The cost is a server: something has to run JavaScript for every request that is not cached. The [rendering guide](https://nuxt.com/docs/4.x/guide/concepts/rendering) calls this default especially suited to content sites: blogs, marketing sites, shops and marketplaces.

The opposite is client-side rendering, a plain single-page app: the server sends an almost empty HTML file and the browser builds the page. You switch the whole app to it with `ssr: false` in `nuxt.config.ts`. It is cheaper to host and simpler to write, because no code runs on a server, but the first view waits for JavaScript and crawlers see little.

## Build a static site with nuxt generate

If every page can be built ahead of time, you do not need a server at all. `nuxt generate` builds the app, starts it once, and renders pages to HTML files. This is static site generation, often shortened to SSG. Run it, then preview the result with `serve`, a small static file server from npm:

```
npx nuxt generate
npx serve .output/public
```

Nuxt finds the pages by crawling, as the [prerendering guide](https://nuxt.com/docs/4.x/getting-started/prerendering) describes. It renders `/`, every non-dynamic page in `app/pages/`, and any routes you list, then follows every `<a href>` it finds in the HTML and repeats until there are no new links. A dynamic route such as `/products/[id]` has no fixed address, so its pages are only built when a link points at them. List the ones no link reaches:

*nuxt.config.ts*

```
export default defineNuxtConfig({
  nitro: {
    prerender: {
      routes: ['/sitemap.xml', '/products/42'],
    },
  },
})
```

The output in `.output/public` is plain files, ready for any static host. Two consequences are easy to miss. Your `server/api` routes do not exist once the site is live, because there is no server, so pages must get everything they need at build time. And the content only changes when you build again.

## Set a rendering mode per route with routeRules

Most real apps sit between these two extremes, and `routeRules` lets you choose per route. Nuxt calls this hybrid rendering. Rules are keyed by path, and `**` matches everything below a path:

*nuxt.config.ts*

```
export default defineNuxtConfig({
  routeRules: {
    // Built once at build time, served as static files
    '/': { prerender: true },
    '/pricing': { prerender: true },
    '/blog/**': { prerender: true },
    // Rendered on request, cached for an hour, refreshed in the background
    '/products/**': { swr: 3600 },
    // Rendered only in the browser, with its own layout, kept out of search
    '/dashboard/**': {
      ssr: false,
      appLayout: 'dashboard',
      headers: { 'x-robots-tag': 'noindex' },
    },
    // Old URLs, moved for good
    '/shop/**': { redirect: { to: '/products/**', statusCode: 301 } },
  },
})
```

Here is what each rule does:

- `prerender: true` renders the route during the build and serves the result as a static file.
- `swr: 3600` (stale-while-revalidate) caches the server's response for 3,600 seconds. After that, the next visitor still gets the cached page while Nuxt renders a fresh one in the background. Nitro's `node-server` output can cache the full response itself.
- `isr` (incremental static regeneration) works like `swr`, but also puts the response in the hosting platform's CDN cache, on the platforms that support it; the docs name Netlify and Vercel. `isr: true` keeps the page cached until your next deploy.
- `ssr: false` skips server rendering for those routes only. The browser gets an empty shell and builds the page itself.
- `appLayout` picks the layout from `app/layouts/` for every matching route, here `app/layouts/dashboard.vue`, without a `definePageMeta` call in each page.
- `headers` adds response headers.
- `redirect` sends a redirect from the server. It is a temporary 307 unless you pass a `statusCode`, and a trailing `/**` on both sides carries the rest of the path across, so `/shop/42` goes to `/products/42`.

A `**` prerender rule does not find every page under `nuxt build`. Nuxt prerenders the non-dynamic pages it matches, but blog posts from a dynamic page such as `app/pages/blog/[slug].vue` are only found through links, and `nuxt build` follows `<a href>` links only when you set `nitro.prerender.crawlLinks: true` (`nuxt generate` turns it on for you).

That switch is a blunt fix in an app like this one: the crawler then prerenders every page it reaches through a link, product pages included, unless their rule says `prerender: false`. List the posts in `nitro.prerender.routes` instead, then check the build log: it lists every route it prerendered, and a post missing from it was not built.

Routes with `swr` or `isr` also write a `_payload.json` next to the HTML, so client-side navigation to a cached page reuses the cached data instead of calling your API again.

## Choose a rule for each kind of page

Keep universal rendering as the default and add a rule only where a kind of page gives you a reason. Two questions decide which rule: is the page the same for every visitor, and how often does it change?

- **Marketing, docs and blog pages** are the same for everyone and change when you deploy. Use `prerender: true`. They cost nothing to serve and load fastest.
- **Product and catalogue pages** are the same for everyone, must be indexed, and change during the day. There are often too many to build on every deploy. Use `swr` with a time that matches how stale a page may be, or `isr` on Netlify or Vercel.
- **Dashboards, account and admin pages** are different for each user, sit behind a login and have no search value. Use `ssr: false`. Server rendering would only add work, and there is nothing on the page for a crawler anyway.
- **Everything else**, such as search results or pages that mix personal and public content, keeps the default.

## What each mode needs from your host

The command you build with decides what you can deploy:

- `nuxt generate` produces static files only. Any static host or CDN can serve them. Server routes, `swr`, `isr` and other hybrid rules do not work here, because nothing runs after the build.
- `nuxt build` produces a server, plus static files for the prerendered routes. It needs somewhere to run JavaScript: a long-running Node process, or serverless and edge functions on a platform.
- `isr` needs a platform with a CDN cache that understands it. On your own Node server, use `swr`.

With the default output, you start the server like this, and it listens on port 3000 unless you set `PORT`:

```
npx nuxt build
NODE_ENV=production node .output/server/index.mjs
```

Set `NODE_ENV=production` when you run it. The deployment docs warn that some dependencies, Vue Router among them, only strip development warnings when it is set.

## Pick a deployment preset

Nitro, the server engine inside Nuxt, turns the same app into different outputs through presets. The default is `node-server`. When you run `nuxt generate`, a static preset is used instead. When you build in the CI of a supported platform, including Vercel, Netlify and Cloudflare, Nitro detects it and picks that platform's preset without any configuration. To choose a preset yourself, name it in `nuxt.config.ts`. This one keeps the Node server even where Nitro would detect a platform:

*nuxt.config.ts*

```
export default defineNuxtConfig({
  nitro: {
    preset: 'node-server',
  },
})
```

Or set it for a single build, which is easier in CI: `NITRO_PRESET=node-cluster npx nuxt build`. If both are set, the variable wins for that build.

Nitro 2.13 ships presets for many hosts, among them `node-cluster` for several Node processes, `bun`, `deno-server`, `aws-lambda`, `cloudflare-module` and `vercel`. The [deployment guide](https://nuxt.com/docs/4.x/getting-started/deployment) links to the full list. Your route rules travel with the preset: on Netlify and Vercel, Nuxt writes them into the platform's own routing and cache rules where it can.

**Check it stuck**3 questions from the Rendering modes and deployment quiz.

[**Practice now**](https://vuejs.school/learn/nuxt/rendering-modes-and-deployment?tab=quiz)

**Was this helpful?**

## **Keep reading**

[Release notes · 7 min read**What's new in Nuxt 4.6: nuxt/server, typed $fetch, cookie sessions and Vapor**](https://vuejs.school/community/blog/whats-new-in-nuxt-4-6) [Tutorial · 6 min read**useFetch or $fetch? Picking the right one in Nuxt**](https://vuejs.school/community/blog/usefetch-or-fetch-picking-the-right-one-in-nuxt)

```json
{"@context":"https://schema.org","@graph":[{"@id":"https://vuejs.school/#website","@type":"WebSite","description":"A free AI-instructed school for Vue.js. Short tutorials, runnable code and quizzes that adapt to you.","name":"vuejs.school","url":"https://vuejs.school/","publisher":{"@id":"https://vuejs.school/#identity"}},{"@id":"https://vuejs.school/community/blog/rendering-modes-in-nuxt-ssr-ssg-isr-and-routerules#webpage","@type":"WebPage","dateModified":"2026-10-27T19:31:40.253Z","datePublished":"2026-06-02","description":"Server rendering, static generation, cached pages and client-only routes, what each needs from your host, and how routeRules mixes them in one Nuxt app.","name":"Rendering modes in Nuxt: SSR, SSG, ISR and routeRules","url":"https://vuejs.school/community/blog/rendering-modes-in-nuxt-ssr-ssg-isr-and-routerules","breadcrumb":{"@id":"https://vuejs.school/community/blog/rendering-modes-in-nuxt-ssr-ssg-isr-and-routerules#breadcrumb"},"isPartOf":{"@id":"https://vuejs.school/#website"},"potentialAction":[{"@type":"ReadAction","target":["https://vuejs.school/community/blog/rendering-modes-in-nuxt-ssr-ssg-isr-and-routerules"]}],"primaryImageOfPage":{"@id":"https://vuejs.school/#logo"}},{"@id":"https://vuejs.school/#identity","@type":"Organization","description":"A free AI-instructed school for Vue.js. Short tutorials, runnable code and quizzes that adapt to you.","name":"vuejs.school","url":"https://vuejs.school","logo":{"@id":"https://vuejs.school/#logo"}},{"@id":"https://vuejs.school/community/blog/rendering-modes-in-nuxt-ssr-ssg-isr-and-routerules#article","dateModified":"2026-10-27T19:31:40.253Z","datePublished":"2026-06-02","description":"Server rendering, static generation, cached pages and client-only routes, what each needs from your host, and how routeRules mixes them in one Nuxt app.","headline":"Rendering modes in Nuxt: SSR, SSG, ISR and routeRules","inLanguage":"en","isAccessibleForFree":true,"thumbnailUrl":"https://vuejs.school/_og/d/a_~UmVuZGVyaW5nIG1vZGVzIGluIE51eHQ6IFNTUiwgU1NHLCBJU1IgYW5kIHJvdXRlUnVsZXMgwrcgdnVlanMuc2Nob29s%2Cc_OgCard.takumi%2Ctitle_~UmVuZGVyaW5nIG1vZGVzIGluIE51eHQ6IFNTUiwgU1NHLCBJU1IgYW5kIHJvdXRlUnVsZXM%2Cdescription_~U2VydmVyIHJlbmRlcmluZywgc3RhdGljIGdlbmVyYXRpb24sIGNhY2hlZCBwYWdlcyBhbmQgY2xpZW50LW9ubHkgcm91dGVzLCB3aGF0IGVhY2ggbmVlZHMgZnJvbSB5b3VyIGhvc3QsIGFuZCBob3cgcm91dGVSdWxlcyBtaXhlcyB0aGVtIGluIG9uZSBOdXh0IGFwcC4%2Ceyebrow_~VHV0b3JpYWwgwrcgNyBtaW4gcmVhZA%2Ctint_sky%2Cp_Ii9jb21tdW5pdHkvYmxvZy9yZW5kZXJpbmctbW9kZXMtaW4tbnV4dC1zc3Itc3NnLWlzci1hbmQtcm91dGVydWxlcyI%2Cs_XjPttColquP816Ua.png","@type":["Article","BlogPosting"],"articleSection":["Tutorial"],"author":{"@id":"https://vuejs.school/#identity"},"image":{"@id":"https://vuejs.school/#/schema/image/1"},"isPartOf":{"@id":"https://vuejs.school/community/blog/rendering-modes-in-nuxt-ssr-ssg-isr-and-routerules#webpage"},"mainEntityOfPage":{"@id":"https://vuejs.school/community/blog/rendering-modes-in-nuxt-ssr-ssg-isr-and-routerules#webpage"},"publisher":{"@id":"https://vuejs.school/#identity"}},{"@id":"https://vuejs.school/community/blog/rendering-modes-in-nuxt-ssr-ssg-isr-and-routerules#breadcrumb","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","name":"Blog","item":"https://vuejs.school/community/blog","position":1},{"@type":"ListItem","name":"Rendering modes in Nuxt: SSR, SSG, ISR and routeRules","position":2}]},{"@id":"https://vuejs.school/#logo","@type":"ImageObject","caption":"vuejs.school","contentUrl":"https://vuejs.school/apple-touch-icon.png","inLanguage":"en","url":"https://vuejs.school/apple-touch-icon.png"},{"@id":"https://vuejs.school/#/schema/image/1","@type":"ImageObject","contentUrl":"https://vuejs.school/_og/d/a_~UmVuZGVyaW5nIG1vZGVzIGluIE51eHQ6IFNTUiwgU1NHLCBJU1IgYW5kIHJvdXRlUnVsZXMgwrcgdnVlanMuc2Nob29s%2Cc_OgCard.takumi%2Ctitle_~UmVuZGVyaW5nIG1vZGVzIGluIE51eHQ6IFNTUiwgU1NHLCBJU1IgYW5kIHJvdXRlUnVsZXM%2Cdescription_~U2VydmVyIHJlbmRlcmluZywgc3RhdGljIGdlbmVyYXRpb24sIGNhY2hlZCBwYWdlcyBhbmQgY2xpZW50LW9ubHkgcm91dGVzLCB3aGF0IGVhY2ggbmVlZHMgZnJvbSB5b3VyIGhvc3QsIGFuZCBob3cgcm91dGVSdWxlcyBtaXhlcyB0aGVtIGluIG9uZSBOdXh0IGFwcC4%2Ceyebrow_~VHV0b3JpYWwgwrcgNyBtaW4gcmVhZA%2Ctint_sky%2Cp_Ii9jb21tdW5pdHkvYmxvZy9yZW5kZXJpbmctbW9kZXMtaW4tbnV4dC1zc3Itc3NnLWlzci1hbmQtcm91dGVydWxlcyI%2Cs_XjPttColquP816Ua.png","inLanguage":"en","url":"https://vuejs.school/_og/d/a_~UmVuZGVyaW5nIG1vZGVzIGluIE51eHQ6IFNTUiwgU1NHLCBJU1IgYW5kIHJvdXRlUnVsZXMgwrcgdnVlanMuc2Nob29s%2Cc_OgCard.takumi%2Ctitle_~UmVuZGVyaW5nIG1vZGVzIGluIE51eHQ6IFNTUiwgU1NHLCBJU1IgYW5kIHJvdXRlUnVsZXM%2Cdescription_~U2VydmVyIHJlbmRlcmluZywgc3RhdGljIGdlbmVyYXRpb24sIGNhY2hlZCBwYWdlcyBhbmQgY2xpZW50LW9ubHkgcm91dGVzLCB3aGF0IGVhY2ggbmVlZHMgZnJvbSB5b3VyIGhvc3QsIGFuZCBob3cgcm91dGVSdWxlcyBtaXhlcyB0aGVtIGluIG9uZSBOdXh0IGFwcC4%2Ceyebrow_~VHV0b3JpYWwgwrcgNyBtaW4gcmVhZA%2Ctint_sky%2Cp_Ii9jb21tdW5pdHkvYmxvZy9yZW5kZXJpbmctbW9kZXMtaW4tbnV4dC1zc3Itc3NnLWlzci1hbmQtcm91dGVydWxlcyI%2Cs_XjPttColquP816Ua.png"}]}
```