Rendering Modes in Nuxt: SSR, SSG, and Hybrid

How SSR, SSG, and hybrid rendering affect Google indexing. Which mode to use and when in Nuxt.
Harlan WiltonHarlan Wilton Published Updated

Google can render JavaScript. But it's slower and less reliable than HTML. Choose the wrong rendering mode and your content might not get indexed.

Rendering Modes in Nuxt

Nuxt defaults to SSR (Server-Side Rendering). Every page generates HTML on the server by default. You can override this per route using routeRules.

SSR (Server-Side Rendering) - Server sends HTML, JavaScript hydrates (default) SSG (Static Site Generation) - Pre-render HTML at build time SPA (Single Page Application) - JavaScript renders everything client-side Hybrid - Mix modes per route using routeRulesISR (Incremental Static Regeneration) - Serve cached HTML, rebuild in background

SSR: Server-Side Rendering

Default behavior in Nuxt. Server generates HTML on each request. Google gets immediate content.

nuxt.config.ts
export default defineNuxtConfig({
  ssr: true // This is already the default
})

Good for:

  • Dynamic content (user profiles, dashboards)
  • Personalized pages
  • Real-time data
  • Content updated frequently

Don't use for:

  • Static blogs (SSG is faster)
  • Documentation (SSG is cheaper)
  • Landing pages (SSG serves from CDN)

Performance note: SSR requires server compute on every request. SSG serves pre-built files from CDN.

SSG: Static Site Generation

Pre-render routes at build time. Run nuxt generate or configure prerendering in nuxt.config.ts:

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

Or use routeRules for wildcard patterns:

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

Good for:

  • Blogs
  • Documentation
  • Marketing pages
  • Product catalogs
  • Content updated daily or less

Don't use for:

  • User-specific content
  • Real-time dashboards
  • Content changing hourly
  • Sites with 10,000+ dynamic pages

Build consideration: A site with 50,000 routes might take 30+ minutes to build. Use SSR or ISR instead.

SPA: Client-Side Rendering

Disable SSR for client-only rendering:

nuxt.config.ts
export default defineNuxtConfig({
  ssr: false
})

Or per route:

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

Google's crawler downloads JavaScript, waits for execution, then indexes. This adds 5-10 seconds to indexing time.

Use it for: Internal dashboards, apps behind authentication, content that shouldn't be indexed.

Avoid it for: Marketing pages, blog posts, product catalogs—anything you want Google to index quickly.

Common mistake: Launching a SPA site expecting Google to index it immediately. You'll wait weeks longer than SSR.

Hybrid: Per-Route Configuration

Mix rendering modes based on route needs using routeRules:

nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    // Prerender static content at build time
    '/': { prerender: true },
    '/blog/**': { prerender: true },
    '/docs/**': { prerender: true },

    // SSR for dynamic content
    '/user/**': { ssr: true },
    '/dashboard/**': { ssr: true },

    // SPA for auth-only sections
    '/admin/**': { ssr: false },

    // ISR: Cache with revalidation
    '/products/**': {
      swr: 3600 // Revalidate every hour
    },

    // ISR with longer cache
    '/news/**': {
      swr: 86400 // Revalidate daily
    }
  }
})

This is Nuxt's most powerful feature for SEO. You get SSG speed for static content, SSR flexibility for dynamic content, and SPA efficiency for authenticated sections—all in one app.

ISR: Incremental Static Regeneration

Serve cached HTML, rebuild in background. Best of SSG speed + SSR freshness:

nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    '/blog/**': {
      swr: 3600 // Cache for 1 hour, rebuild in background
    }
  }
})

Google sees instant HTML (like SSG), but content stays fresh (like SSR).

Use it for:

  • Product pages that change occasionally
  • Blog posts with view counts
  • News articles with updated timestamps
  • Any content where 1-hour staleness is acceptable

How it works:

  1. First request: Server renders HTML, caches for 1 hour
  2. Next requests: Serve cached HTML instantly
  3. After 1 hour: Next request triggers background rebuild while serving stale cache
  4. Cache updates when rebuild completes

Verifying What Google Sees

Don't guess. Check what Googlebot actually received:

1. Google Search Console URL Inspection

  • Go to URL Inspection tool
  • Enter your URL
  • Click "Test Live URL"
  • View "Screenshot" and "HTML" tabs

If the HTML tab shows <div id="__nuxt"></div> with no content, Google isn't seeing your page.

2. View Page Source

Right-click page > "View Page Source" (not Inspect Element)

Good: Full content in HTML ❌ Bad: Empty div with JavaScript

3. Fetch as Google

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

Should return complete HTML with content.

4. Check Build Output

When running nuxt build, look for prerendered routes:

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

If routes you expect to be prerendered aren't listed, check your routeRules configuration.

Common Mistakes

Mistake 1: Using SPA for a blog You're making Google work 10x harder. SSG renders in milliseconds.

Mistake 2: SSR for static documentation Why run server compute for content that never changes? SSG is free to serve.

Mistake 3: SSG for 100,000 product pages Your builds will time out. Use SSR or ISR instead.

Mistake 4: Forgetting route rules Nuxt defaults to SSR. Without explicit routeRules, builds won't prerender anything—you'll need a server for all requests.

Mistake 5: Not testing the output Always verify with Search Console. Your local dev server lies.

Mistake 6: Mixing conflicting route rules More specific patterns override general ones. Order matters:

// ❌ Wrong: Specific rule comes after general
routeRules: {
  '/blog/**': { prerender: true },
  '/blog/draft': { ssr: false } // Won't work, already matched above
}

// ✅ Right: Specific before general
routeRules: {
  '/blog/draft': { ssr: false },
  '/blog/**': { prerender: true }
}

Default Recommendation

Start with this configuration for content-heavy sites:

nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    // Static marketing pages
    '/': { prerender: true },
    '/about': { prerender: true },
    '/contact': { prerender: true },

    // Blog and docs
    '/blog/**': { prerender: true },
    '/docs/**': { prerender: true },

    // Products with ISR
    '/products/**': {
      swr: 3600 // Fresh within 1 hour
    },

    // User-specific content
    '/dashboard/**': { ssr: true },
    '/account/**': { ssr: true },

    // Admin panel (no SEO needed)
    '/admin/**': { ssr: false }
  }
})

Adjust based on your content update frequency.

Nuxt Content Integration

When using @nuxt/content, enable crawler to auto-discover routes:

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

Nuxt will crawl your site starting from /, discovering all <NuxtLink> references and prerendering them automatically.

For dynamic routes:

nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    prerender: {
      routes: async () => {
        // Generate routes from content
        const { $content } = useNuxtApp()
        const posts = await $content('blog').find()
        return posts.map(post => `/blog/${post.slug}`)
      }
    }
  }
})

Deployment Considerations

Different hosts support different rendering modes:

PlatformSSRSSGISR/SWR
Netlify✅ (via functions)
Vercel✅ (ISR built-in)
Cloudflare Pages✅ (via KV)
Static hosts (S3, GitHub Pages)
Node.js server⚠️ (needs cache layer)

Choose your rendering strategy based on your hosting platform.