---
title: "Rendering Modes for SEO in Vue"
description: "Compare SSR, SSG, and CSR for Vue SEO, and see why plain Vue apps outsource per-route rendering to a framework like Nuxt."
canonical_url: "https://nuxtseo.com/learn-seo/vue/routes-and-rendering/rendering"
last_updated: "2026-07-16"
---

<key-takeaways>

- Rendering mode decides whether a crawler's first HTTP response already has your content, or an empty shell that only fills in after JavaScript runs
- Vue ships the building blocks for SSR (`createSSRApp`, `renderToString`), but Vue's own docs recommend a framework for production SSR rather than hand-rolling it
- CSR (a plain Vite SPA) is fine for logged-in areas nobody needs to rank, but it's the wrong default for public pages
- Vapor Mode (experimental, stable-track in Vue 3.6) removes the Virtual DOM overhead that makes hydration expensive in the first place

</key-takeaways>

Rendering mode decides whether a crawler's first HTTP response already contains your content, or an empty `<div id="app"></div>` that only fills in after JavaScript runs. Vue supports every mode on this page, but which ones are easy to set up depends heavily on your tooling.

## 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
    </td>
    
    <td>
      Full HTML, cached and revalidated on a timer
    </td>
    
    <td>
      Content that changes but not every request
    </td>
  </tr>
  
  <tr>
    <td>
      CSR (SPA)
    </td>
    
    <td>
      Empty shell, JS renders the page
    </td>
    
    <td>
      Authenticated dashboards, admin tools
    </td>
  </tr>
</tbody>
</table>

## Server-Side Rendering (SSR)

Vue provides `createSSRApp()` and `renderToString()` for rendering components to an HTML string on the server:

```ts
import { createSSRApp } from 'vue'
import { renderToString } from 'vue/server-renderer'

const app = createSSRApp({
  data: () => ({ count: 1 }),
  template: `<button @click="count++">{{ count }}</button>`
})

const html = await renderToString(app)
```

That's the whole mechanism, but a production app needs dual client/server builds, universal routing, universal data fetching, and hydration-safe state, none of which this snippet handles. [Vue's own SSR guide says as much](https://vuejs.org/guide/scaling-up/ssr.html): a complete implementation is complex enough that Vue "highly recommends using Vue frameworks if you need SSR since they often have built-in SSR support." Nuxt is that framework for Vue; see [Rendering Modes in Nuxt](/learn-seo/nuxt/routes-and-rendering/rendering) for the per-route version of this guide.

## Static Site Generation (SSG)

Static generation renders every route once at build time and serves the output from a CDN. For Vue content sites, that generally means reaching for a static-site tool built on Vue (VitePress for docs, Nuxt with `prerender: true` for general apps) rather than assembling a build-time renderer by hand.

Good for docs, marketing pages, and anything that changes at deploy time rather than per request. It stops scaling once you're generating tens of thousands of routes, since every page adds to the build and a full rebuild for one content change gets slow.

## Incremental Static Regeneration (ISR)

A hybrid: the static version serves instantly, then re-renders in the background once it's stale, without a full rebuild. Plain Vue + [Vite](https://vite.dev) has no built-in ISR; it's a hosting-platform or framework feature (Nuxt's `swr`/`isr` route rules, or your CDN's own stale-while-revalidate support).

## Client-Side Rendering (CSR)

The default for a Vue app created with `create-vue` or a plain Vite SPA template: the server sends an empty HTML shell, and Vue renders everything in the browser.

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, internal tools. See [Meta Tags](/learn-seo/vue/controlling-crawlers/meta-tags) for setting `noindex` explicitly, and [Indexing Issues](/learn-seo/vue/launch-and-listen/indexing-issues) if a CSR route ends up indexed by mistake.

## Vapor Mode and Hydration Cost

Hydration is what makes a client-rendered (or SSR'd) Vue app slow to become interactive: the browser has to walk the DOM Vue already produced and attach reactivity to it before clicks and inputs do anything. Vue's Vapor Mode compiles components without the Virtual DOM, which cuts that hydration overhead. It's experimental and stable-track for Vue 3.6, not yet the default: worth watching if hydration cost shows up in your Interaction to Next Paint numbers, not something to build on for production today.

## 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. **Disable JavaScript** and reload. SSR/SSG content stays visible; a CSR app goes blank, which is close to what non-rendering AI crawlers see

## Checklist

<checklist id="vue-rendering">

- Public, non-personalized pages are server-rendered or statically generated, not CSR
- "View Page Source" shows real content on every indexable route
- CSR routes (if any) are intentionally `noindex`, not accidental
- If you need SSR/SSG/ISR without building it yourself, you've evaluated Nuxt

</checklist>
