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

Nuxt SEO on GitHub

# **Rendering Modes for SEO in Nuxt**

Pick SSR, SSG, ISR, or islands per route with Nuxt's routeRules, then confirm Googlebot actually sees the rendered HTML.

[Harlan Wilton](https://x.com/harlan-zw)9 mins read Published **Oct 25, 2024** Updated **Jul 16, 2026**

**What you'll learn**

- 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

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

| **Mode** | **First response** | **Best for** |
| --- | --- | --- |
| SSR | Full HTML, rendered per request | Personalized or frequently-changing pages |
| SSG / prerender | Full HTML, built once at deploy time | Docs, marketing pages, blog posts |
| ISR (`**swr**`) | Full HTML, cached and revalidated on a timer | Content that changes but not every request |
| CSR (`**ssr: false**`) | Empty shell, JS renders the page | Authenticated dashboards, admin tools |

## Server-Side Rendering (SSR)

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

nuxt.config.ts

```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:

nuxt.config.ts

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

Or `**routeRules**` for path patterns:

nuxt.config.ts

```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:

nuxt.config.ts

```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**`:

nuxt.config.ts

```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:

nuxt.config.ts

```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~~**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/meta-tags) for setting that explicitly, and [**~~Indexing Issues~~**](https://nuxtseo.com/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:

nuxt.config.ts

```ts
export default defineNuxtConfig({
  experimental: {
    componentIslands: true
  }
})
```

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

components/ArticleBody.server.vue

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

pages/\[slug].vue

```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.

nuxt.config.ts

```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
```

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

```
ℹ 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**

- 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

[**The 2026 SEO Checklist for Nuxt & Vue ** Pre-launch setup, post-launch verification, and ongoing monitoring. Interactive checklist with links to every guide.](https://nuxtseo.com/learn-seo/checklist) [Haven't launched yet? Start with the **Pre-Launch Warmup**](https://nuxtseo.com/learn-seo/pre-launch-warmup)

---

### **Related **

[**Core Web Vitals**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/core-web-vitals)

[**Indexing Issues**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/indexing-issues)

[**Meta Tags**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/meta-tags)

[**SPA SEO**](https://nuxtseo.com/learn-seo/spa-seo)

[**Internal Linking** Build a NuxtLink hub-and-spoke structure so Googlebot reaches every page instead of leaving routes orphaned in your sitemap.](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/internal-linking) [**Programmatic SEO** Generate SEO-ready feature, comparison, and alternative pages in Nuxt without triggering Google's scaled content penalties.](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/programmatic-seo)

**On this page**

- [Rendering Modes at a Glance](#rendering-modes-at-a-glance)
- [Server-Side Rendering (SSR)](#server-side-rendering-ssr)
- [Static Site Generation (SSG)](#static-site-generation-ssg)
- [Incremental Static Regeneration (ISR)](#incremental-static-regeneration-isr)
- [Client-Side Rendering (CSR)](#client-side-rendering-csr)
- [Server Components (Islands)](#server-components-islands)
- [Mixing Modes with routeRules](#mixing-modes-with-routerules)
- [Verifying What Google Sees](#verifying-what-google-sees)
- [Checklist](#checklist)