What are rendering modes in Nuxt (SSR, SSG, SPA, Hybrid)?
Nuxt rendering modes control whether pages are generated on the server per request (SSR), pre-built at build time (SSG), assembled entirely in the browser (SPA), or handled differently per route (Hybrid).
Theory
TL;DR
- SSR = a restaurant kitchen cooking fresh per order. SSG = meals pre-cooked and waiting. SPA = ingredients shipped to you to assemble at home. Hybrid = pick the approach per dish.
- Main split: SSR and SSG deliver complete HTML from the server. SPA ships a JS bundle and the browser builds the page itself.
- SEO + dynamic data? SSR. Static content + CDN speed? SSG. Dashboard behind login? SPA. Mixed site? Hybrid with
routeRules. - Nuxt uses Nitro as the underlying engine for all four modes.
Quick example
// nuxt.config.ts - the four modes in one file
export default defineNuxtConfig({
// SSR (default): server renders HTML on each request
ssr: true,
// SSG: run `npx nuxi generate` - outputs /dist with static HTML files
// ssr: true + `nuxi generate` command
// SPA: browser handles everything, server ships empty shell
// ssr: false
// Hybrid (Nuxt 3): per-route rules
routeRules: {
'/blog/**': { ssr: true }, // SSR for SEO
'/dashboard/**': { ssr: false } // SPA for interactivity
}
})Run npx nuxi dev with ssr: true and you see server logs on every page request. Switch to ssr: false and the logs go quiet. The browser is now doing all the work.
Key difference
SSR and SSG both produce real HTML that search crawlers and users receive immediately. The difference is timing: SSR generates that HTML at request time, SSG generates it at build time. SPA skips HTML generation entirely and ships a JS bundle, so the browser has to download, parse, and execute JavaScript before any content appears. That delay hurts SEO and perceived performance.
When to use
- SEO + frequently updated content: SSR. News sites, e-commerce product pages, any page where data changes hourly.
- Static content, CDN speed: SSG. Documentation, marketing pages, landing pages. nuxt.com itself uses SSG via
nuxi generate. - Interactive app, no SEO requirements: SPA. Admin panels, dashboards, anything behind authentication.
- Mixed site (blog + admin): Hybrid with
routeRules. Blog routes get SSR, admin routes get SPA behavior. - User-specific data: SSR or Hybrid, using
useAsyncDataoruseFetchserver-side.
Comparison table
| Feature | SSR | SSG | SPA | Hybrid |
|---|---|---|---|---|
| HTML delivery | Server per request | Build-time static files | Empty shell + JS | Per-route choice |
| SEO | Excellent | Excellent | Poor (JS-dependent) | Route-specific |
| TTFB | Medium (server compute) | Fastest (CDN edge) | Slow (JS download + exec) | Optimized per route |
| Dynamic data | Full (useAsyncData) | Build-time only | Client fetches | Mix |
| Build time | Fast | Scales with page count | Fastest | Like SSR + static routes |
| Server cost | High (per request) | None (CDN) | None after deploy | Low (only SSR routes) |
| Best for | E-commerce, blogs | Docs, landing pages | Admin panels | Most real-world apps |
How Nitro handles this
Nitro, Nuxt's server engine, compiles Vue components into server renderers for SSR and SSG. For SSR, Nitro executes useAsyncData and useFetch against Node.js APIs on each incoming request, serializes the result to HTML, and injects a JSON payload into <script> tags for client-side hydration. For SSG, it does the same thing once at build time and writes .html files. SPA mode skips Nitro's renderer entirely. Vite bundles the app for the browser. Hybrid mode scans routeRules at build and routes SSR paths to Nitro handlers while SPA paths get a client entrypoint.
The full sequence for a Hybrid SSR route like /blog/post: request hits Nitro, matches the ssr: true rule, renders Vue to an HTML string via @vue/server-renderer, executes useAsyncData in Node, injects the JSON payload into <script>, then streams the full HTML response.
Common mistakes
1. Setting ssr: false on a site that needs SEO.
// Wrong: search crawlers get an empty div
export default defineNuxtConfig({ ssr: false })
// curl http://localhost:3000 → <div id="__nuxt"></div>
// Fix: keep SSR on and verify with curl
export default defineNuxtConfig({ ssr: true })
// curl http://localhost:3000 → <article>Full post content here</article>2. Using useFetch in SSG for external APIs that do not exist at build time.
// Wrong: build fails because the API is not available during nuxi generate
const { data } = await useFetch('/api/live-prices')
// Fix: skip server execution
const { data } = await useFetch('/api/live-prices', { server: false })3. Using client-only components without <ClientOnly> in SSR routes.
A component that references window or document breaks server rendering. Wrap it:
<ClientOnly>
<HeavyChartComponent />
</ClientOnly>4. Running nuxi build instead of nuxi generate for SSG deploys.
nuxi build creates an SSR Node.js server. nuxi generate produces static HTML files for CDN. Wrong command means wrong output type and a confused deployment pipeline.
5. Expecting SSG to handle unknown dynamic params.
If you pre-generate /users/1, /users/2, /users/3 at build time and someone requests /users/99, they get a 404. SSG bakes in only the pages it knows about. For all possible IDs, SSR is the right call.
Real-world usage
- nuxt.com uses SSG via
nuxi generate, deployed to CDN. - Vercel auto-detects SSG routes from
routeRulesin Hybrid deploys. - E-commerce sites use SSR for cart and checkout, SSG for product listings.
- Contentful + Nuxt setups use SSR blogs with
useAsyncData. - Admin dashboards apply
ssr: falseon/admin/**routes.
Follow-up questions
Q: How does Hybrid in Nuxt 3 differ from Nuxt 2's universal mode?
A: Nuxt 2 had a single global SSR + client hydration model. Nuxt 3 Hybrid lets you pick per route via routeRules, so /blog/** can be SSR while /admin/** behaves as SPA, all in one app without any global toggle.
Q: What happens to useState in SSG?
A: At build time, state initializes on the server normally. After hydration, useState persists in browser memory. But since there is no server at runtime, state does not survive page refreshes the way it would in SSR.
Q: How do you simulate ISR in Nuxt?
A: Nuxt has no native ISR like Next.js. The closest option is routeRules with swr: 3600 (stale-while-revalidate), which serves cached content and revalidates in the background. For full static files plus webhook-triggered rebuilds, combine prerender: true with a CI trigger.
Q: How do you debug slow SSR time-to-interactive?
A: Profile Nitro with its dev tools, use useLazyFetch for non-critical data so the initial HTML arrives faster, and wrap heavy third-party widgets in <ClientOnly> so they do not block server rendering.
Q: In Hybrid mode, trace the full sequence for a /blog/post SSR route.
A: Request hits Nitro, matches the ssr: true rule in routeRules, renders Vue to an HTML string via @vue/server-renderer, executes useAsyncData in Node, injects the JSON payload into a <script> tag for hydration, then streams HTML plus payload to the client.
Examples
SSR blog post with server-side data fetching
<!-- pages/blog/[slug].vue -->
<template>
<article>
<h1>{{ post.title }}</h1>
<div v-html="post.body" />
</article>
</template>
<script setup>
const route = useRoute()
// useAsyncData runs on the server - SEO crawlers see full content
const { data: post } = await useAsyncData('post', () =>
$fetch(`/api/posts/${route.params.slug}`)
)
</script>The server executes $fetch against /api/posts/[slug] before sending HTML. View the page source and the title and body are already in the markup, not inserted later by JavaScript.
Hybrid app: blog combined with admin panel
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
'/blog/**': { ssr: true }, // Full SSR: SEO + fresh data
'/dashboard/**': { ssr: false }, // SPA: no crawlers, rich interactions
'/': { prerender: true } // SSG homepage: fastest possible TTFB
}
})<!-- pages/dashboard/analytics.vue - runs client-side only -->
<script setup>
const { data: stats } = await useFetch('/api/analytics', { server: false })
</script>Three different behaviors in one Nuxt app. The homepage is a static file on CDN. Blog posts are SSR from a Node server. The dashboard is a SPA shell that fetches its own data after load.
E-commerce setup with ISR-like caching
// nuxt.config.ts - realistic e-commerce routeRules
export default defineNuxtConfig({
routeRules: {
'/': { prerender: true }, // Static homepage
'/products': { swr: 600 }, // Product list: cache 10 min, revalidate in background
'/products/**': { swr: 3600 }, // Product pages: cache 1 hour
'/cart': { ssr: false }, // SPA cart: user-specific, no SEO needed
'/checkout': { ssr: true }, // SSR checkout: security + personalization
'/admin/**': { ssr: false } // SPA admin panel
}
})swr (stale-while-revalidate) serves cached content immediately and triggers a background refresh after the cache window expires. Product pages load fast from cache while staying reasonably fresh. This is as close to Next.js ISR as Nuxt gets without external rebuild triggers.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.