5 min read
On this page

Rendering Modes

Nuxt gives you four rendering strategies and lets you mix them per-route. The choice matters more than most teams realize — it affects your hosting costs, SEO, time-to-interactive, and how much caching complexity you take on. Pick wrong and you end up with a fully server-rendered marketing site that costs $200/month to host when a static build would do fine, or a client-only dashboard that Google cannot index.

Universal Rendering (SSR)

Universal rendering is the default. Nuxt runs your Vue components on the server, sends HTML to the browser, and then Vue "hydrates" that HTML on the client to make it interactive. The user sees content immediately while JavaScript loads in the background.

// nuxt.config.ts — SSR is the default, but being explicit is fine
export default defineNuxtConfig({
  ssr: true,
})

The server handles the initial page load. Subsequent navigations happen client-side with Vue Router — no full page reloads. This gives you the best of both worlds: fast first paint and SPA-like navigation.

SSR is the right choice when your pages have dynamic content that changes per request (user dashboards, personalized feeds, e-commerce product pages with live inventory) and you need search engines to see that content.

<!-- pages/products/[id].vue -->
<script setup lang="ts">
// This runs on the server for the first request,
// then on the client for subsequent navigations
const route = useRoute()
const { data: product } = await useFetch(`/api/products/${route.params.id}`)
</script>

<template>
  <div v-if="product">
    <h1>{{ product.name }}</h1>
    <p>{{ product.description }}</p>
    <span>{{ product.price }}</span>
  </div>
</template>

The tradeoff: you need a server. That means a Node.js process, a serverless function, or an edge worker running at all times. Every request hits your server, so your infrastructure scales with traffic.

Client-Only Rendering (SPA)

Set ssr: false and Nuxt becomes a traditional single-page application. The server sends a minimal HTML shell, and Vue renders everything in the browser.

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

This is appropriate for internal tools, admin panels, and apps behind authentication where SEO does not matter. Basecamp's admin interface, a Stripe dashboard clone, an internal inventory management tool — these are SPA territory.

The upside is simpler hosting. You deploy static files to any CDN. No server runtime, no cold starts, no scaling concerns. The downside is that users see a blank page (or a loading spinner) until JavaScript downloads, parses, and executes.

<!-- app.vue in a SPA Nuxt app -->
<script setup lang="ts">
// Everything runs in the browser only
const { data: user } = await useFetch('/api/me', {
  headers: useRequestHeaders(['cookie']),
})
</script>

<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>
</template>

Static Site Generation (SSG)

Static generation pre-renders every page at build time. Nuxt crawls your routes, renders each one to HTML, and outputs a directory of static files.

// nuxt.config.ts
export default defineNuxtConfig({
  // Tell Nuxt to prerender all routes at build time
  nitro: {
    prerender: {
      routes: ['/'],
      crawlLinks: true,
    },
  },
})

Run nuxi generate instead of nuxi build. The output is a folder of HTML, CSS, and JS files ready for any static host — Netlify, Vercel, GitHub Pages, an S3 bucket.

SSG works well for blogs, documentation sites, marketing pages, and anything where content changes infrequently. Nuxt Content sites are a natural fit. The content lives in markdown files, changes via git commits, and rebuilds take seconds.

// nuxt.config.ts for a blog
export default defineNuxtConfig({
  modules: ['@nuxt/content'],
  nitro: {
    prerender: {
      routes: ['/'],
      crawlLinks: true,
    },
  },
})

The catch is that every content change requires a rebuild and redeploy. For a 50-page marketing site, that takes under a minute. For a 10,000-product catalog, it can take 20 minutes. At that scale, you start looking at incremental static regeneration or hybrid rendering.

Hybrid Rendering with routeRules

This is where Nuxt gets genuinely powerful. You can assign different rendering strategies to different routes using routeRules. Your marketing pages get prerendered, your dashboard stays client-only, and your product pages use SSR with caching.

// nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    // Static pages — prerendered at build time
    '/': { prerender: true },
    '/about': { prerender: true },
    '/blog/**': { prerender: true },

    // Dashboard — client-side only, no SSR
    '/dashboard/**': { ssr: false },

    // Product pages — SSR with CDN caching for 1 hour
    '/products/**': {
      swr: 3600,
    },

    // API routes — cached at the edge for 10 minutes
    '/api/products/**': {
      cache: { maxAge: 600 },
    },

    // Redirects
    '/old-page': { redirect: '/new-page' },
  },
})

SWR and ISR

The swr (stale-while-revalidate) option is particularly useful. It serves a cached version immediately and revalidates in the background. Users always get a fast response, and the content stays reasonably fresh.

// nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    // Serve stale content for up to 1 hour
    // while revalidating in the background
    '/products/**': { swr: 3600 },

    // ISR — similar to Next.js behavior
    // Regenerate after 60 seconds on the next request
    '/blog/**': { isr: 60 },
  },
})

The difference between swr and isr: SWR always serves the cached version and updates behind the scenes. ISR can serve a stale page but triggers regeneration that blocks if the cache has expired. In practice, SWR gives a better user experience for most use cases.

Per-Route Headers and CORS

routeRules also handles HTTP headers, CORS, and other response-level configuration.

export default defineNuxtConfig({
  routeRules: {
    '/api/**': {
      cors: true,
      headers: {
        'Access-Control-Allow-Origin': 'https://myapp.com',
        'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE',
      },
    },
    '/assets/**': {
      headers: {
        'Cache-Control': 'public, max-age=31536000, immutable',
      },
    },
  },
})

Choosing the Right Mode

The decision tree is straightforward:

Does the page need SEO and has dynamic content that changes per request? Use SSR.

Does the page need SEO but content changes infrequently? Use SSG or prerendering.

Is the page behind authentication where SEO is irrelevant? Use SPA mode.

Do different pages have different requirements? Use hybrid rendering with routeRules.

Most real-world Nuxt apps end up hybrid. A SaaS product might prerender its landing page and docs, serve the app dashboard as a SPA, and use SSR with SWR caching for public-facing user profiles.

Common Pitfalls

Defaulting to SSR when SSG would suffice. If your content does not change between requests, you are paying for server infrastructure you do not need. A docs site or blog should almost always be statically generated.

Ignoring routeRules and going all-or-nothing. Teams often set ssr: false globally because one section of their app does not work with SSR. Use routeRules to disable SSR per-route instead.

Forgetting that SSR means server costs. SSR requires compute on every request. If you are on Vercel or Netlify, that means serverless function invocations. At scale, this adds up. Cache aggressively with swr or isr.

Not testing in production mode locally. nuxi dev behaves differently from nuxi build && nuxi preview. Always test your rendering configuration with a production build before deploying.

Key Takeaways

  • Nuxt defaults to universal rendering (SSR), which works for most apps but is not always the best choice.
  • Use ssr: false for internal tools and dashboards where SEO is irrelevant.
  • Use nuxi generate with prerendering for content-heavy sites with infrequent updates.
  • routeRules lets you mix rendering modes per route — this is the most practical approach for production apps.
  • SWR caching gives you SSR performance with near-static delivery speed.