---
title: "Rendering Modes for SEO in Nuxt"
description: "Pick SSR, SSG, ISR, or islands per route with Nuxt's routeRules, then confirm Googlebot actually sees the rendered HTML."
canonical_url: "https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/rendering"
last_updated: "2026-07-16"
---

<key-takeaways>

- Nuxt doesn't force one rendering mode for the whole site; `routeRules` sets SSR, SSG, ISR, or CSR per path
- SSG and ISR ship complete HTML in the first response, so search engines and non-JS AI crawlers index them without waiting on a render queue
- CSR (`ssr: false`) is fine for logged-in areas nobody needs to rank, but it's the wrong default for anything public
- Server components strip hydration cost from static sections of a page, which helps Interaction to Next Paint

</key-takeaways>

Rendering mode decides whether a crawler's first HTTP response already contains your content, or an empty `<div id="__nuxt"></div>` that only fills in after JavaScript runs. Nuxt lets you set that per route instead of picking one mode for the whole app.

## Rendering Modes at a Glance

<table>
<thead>
  <tr>
    <th>
      Mode
    </th>
    
    <th>
      First response
    </th>
    
    <th>
      Best for
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      SSR
    </td>
    
    <td>
      Full HTML, rendered per request
    </td>
    
    <td>
      Personalized or frequently-changing pages
    </td>
  </tr>
  
  <tr>
    <td>
      SSG / prerender
    </td>
    
    <td>
      Full HTML, built once at deploy time
    </td>
    
    <td>
      Docs, marketing pages, blog posts
    </td>
  </tr>
  
  <tr>
    <td>
      ISR (<code>
        swr
      </code>
      
      )
    </td>
    
    <td>
      Full HTML, cached and revalidated on a timer
    </td>
    
    <td>
      Content that changes but not every request
    </td>
  </tr>
  
  <tr>
    <td>
      CSR (<code>
        ssr: false
      </code>
      
      )
    </td>
    
    <td>
      Empty shell, JS renders the page
    </td>
    
    <td>
      Authenticated dashboards, admin tools
    </td>
  </tr>
</tbody>
</table>

## Server-Side Rendering (SSR)

Nuxt renders SSR by default: `ssr: true` in `nuxt.config.ts` is the implicit setting.

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  ssr: true // the default, shown for clarity
})
```

Good for dashboards, search results, and anything personalized per request. The tradeoff is compute: every request re-renders the page, so SSR costs more than serving a static file and adds server round-trip time to TTFB. Deploying to an edge runtime (Cloudflare Workers, Vercel Edge) keeps that round trip short by running close to the requester instead of a single origin region.

## Static Site Generation (SSG)

Nuxt renders routes once at build time and serves the resulting HTML from a CDN. Use `nitro.prerender.routes` for an explicit list:

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  nitro: {
    prerender: {
      routes: ['/blog', '/docs', '/about']
    }
  }
})
```

Or `routeRules` for path patterns:

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  routeRules: {
    '/blog/**': { prerender: true },
    '/docs/**': { prerender: true }
  }
})
```

Good for docs, marketing pages, and anything that changes at deploy time rather than per request. It doesn't scale to sites with tens of thousands of routes: every prerendered page adds to the build, and a full rebuild for one content change stops being practical well before that. ISR is the fix.

With `@nuxt/content`, let Nitro's crawler discover routes instead of listing every URL by hand:

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  nitro: {
    prerender: {
      crawlLinks: true,
      routes: ['/']
    }
  }
})
```

Nitro starts at `/`, follows every `<NuxtLink>` it finds, and prerenders each page it discovers.

## Incremental Static Regeneration (ISR)

A hybrid: Nuxt serves the cached static version instantly, then re-renders in the background once it goes stale. Set it with `swr` (seconds) or `isr` in `routeRules`:

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  routeRules: {
    '/products/**': { swr: 600 } // revalidate every 10 minutes
  }
})
```

Good for product catalogs and news, where a full rebuild per change is wasteful but content can't go stale indefinitely. `isr` support depends on your deployment target: check your host's Nitro preset docs before relying on it in production.

## Client-Side Rendering (CSR)

`ssr: false` disables server rendering, globally or per route:

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  routeRules: {
    '/admin/**': { ssr: false }
  }
})
```

Googlebot queues client-rendered pages for a separate rendering pass after crawling. Google states the page "may stay on this queue for a few seconds, but it can take longer than that", [with no fixed timeframe published](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics). AI crawlers that don't execute JavaScript (GPTBot, ClaudeBot, PerplexityBot) never see content that only renders client-side at all.

Reserve CSR for routes nobody needs to rank: admin panels, logged-in dashboards, anything you'd `noindex` anyway. See [Meta Tags](/learn-seo/nuxt/controlling-crawlers/meta-tags) for setting that explicitly, and [Indexing Issues](/learn-seo/nuxt/launch-and-listen/indexing-issues) if a CSR route ends up indexed by mistake.

## Server Components (Islands)

Server components render a piece of the page on the server with zero client-side JavaScript, so hydration never touches them. This cuts the amount of JS Nuxt has to execute on the client, which helps Interaction to Next Paint on pages with a lot of static content.

The feature is experimental and needs an opt-in flag:

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  experimental: {
    componentIslands: true
  }
})
```

Name a component `*.server.vue` and use it like any other component; Nuxt wraps it automatically:

```vue [components/ArticleBody.server.vue]
<!-- Rendered on the server, ships no client-side JS -->
<template>
  <article>
    <h1>{{ title }}</h1>
    <slot />
  </article>
</template>
```

```vue [pages/[slug].vue]
<template>
  <div>
    <ArticleBody :title="post.title">
      {{ post.body }}
    </ArticleBody>
    <CommentForm /> <!-- this part still hydrates normally -->
  </div>
</template>
```

## Mixing Modes with routeRules

`routeRules` is what makes per-route rendering practical: static pages get prerendered, catalogs get ISR, dashboards stay client-only, all from one config block.

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  routeRules: {
    '/': { prerender: true },
    '/blog/**': { prerender: true },
    '/products/**': { swr: 600 },
    '/account/**': { ssr: true },
    '/admin/**': { ssr: false }
  }
})
```

More specific patterns must come before general ones, since Nuxt applies the first match:

```ts
// ❌ /blog/draft never gets its own rule; /blog/** already matched it
const broadRuleFirst = {
  routeRules: {
    '/blog/**': { prerender: true },
    '/blog/draft': { ssr: false }
  }
}

// ✅ specific pattern first
const specificRuleFirst = {
  routeRules: {
    '/blog/draft': { ssr: false },
    '/blog/**': { prerender: true }
  }
}
```

Full reference: [Nuxt rendering docs](https://nuxt.com/docs/guide/concepts/rendering).

## Verifying What Google Sees

Don't guess. Check what Googlebot received:

1. **View Page Source** (not "Inspect Element"). If your content is missing from the raw HTML, you're relying on client-side rendering
2. **URL Inspection in Search Console.** "Test Live URL" and compare the crawled HTML against the rendered HTML tab
3. **Fetch with a Googlebot user agent:**

```bash
curl -A "Mozilla/5.0 (compatible; Googlebot/2.1)" https://yoursite.com
```

1. **Check the build output** for routes you expected to prerender:

```text
ℹ Prerendering 42 initial routes with crawler
  ├── /
  ├── /blog/post-1
  └── ...
```

If a route you expected to see isn't listed, check your `routeRules` patterns for one that's shadowing it.

## Checklist

<checklist id="nuxt-rendering">

- Public, non-personalized pages use SSR, SSG, or ISR, not CSR
- `routeRules` lists specific patterns before general ones
- "View Page Source" shows real content on every indexable route
- CSR routes (if any) are intentionally `noindex`, not accidental
- Build output confirms the routes you expect are prerendered

</checklist>
