Skip to main content

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

typescript
// 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 useAsyncData or useFetch server-side.

Comparison table

FeatureSSRSSGSPAHybrid
HTML deliveryServer per requestBuild-time static filesEmpty shell + JSPer-route choice
SEOExcellentExcellentPoor (JS-dependent)Route-specific
TTFBMedium (server compute)Fastest (CDN edge)Slow (JS download + exec)Optimized per route
Dynamic dataFull (useAsyncData)Build-time onlyClient fetchesMix
Build timeFastScales with page countFastestLike SSR + static routes
Server costHigh (per request)None (CDN)None after deployLow (only SSR routes)
Best forE-commerce, blogsDocs, landing pagesAdmin panelsMost 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.

typescript
// 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.

typescript
// 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:

vue
<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 routeRules in 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: false on /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

vue
<!-- 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

typescript
// 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 } })
vue
<!-- 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

typescript
// 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 ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?