Using SSR, SSG and Hybrid Rendering in Nuxt the Right Way
AI generated
<v/>
{ }
Nuxt · Vue.js · SSR · SSG · Hybrid Rendering
SSR, SSG and Hybrid Rendering in Nuxt 3
Choosing the right rendering mode for every route

Nuxt 3 offers server side rendering, static site generation, incremental static regeneration and hybrid rendering, all in one framework. Anyone who does not know the differences between the modes gives away performance and SEO potential. This article explains all rendering modes with concrete route rule configurations and typical pitfalls.

16 min read SSR · SSG · ISR · Route Rules · Hydration · Caching Nuxt 3 · Vue 3 · Nitro · TypeScript

1. Rendering modes in Nuxt 3: overview and decision framework

Nuxt 3 is the Vue framework with the broadest rendering spectrum: the same codebase can produce server side rendering, static site generation, incremental static regeneration and client side rendering, depending on route configuration. That makes Nuxt the foundation for practically every type of web application, but it also creates confusion when the differences between the modes are not clear. Choosing the wrong rendering mode in Nuxt can mean that an otherwise cacheable marketing page is server rendered on every single request, or that a dashboard with user-specific data is statically generated and shows identical content to every user.

The decision framework for SSR, SSG and hybrid rendering in Nuxt depends on two questions: how often does a route's data change, and is the data user-specific? Static content such as landing pages, blog posts and product pages benefits from SSG or ISR. User-specific data such as shopping carts, dashboards and profiles needs SSR or CSR. Hybrid rendering in Nuxt 3 makes it possible to make this decision route by route, without splitting the application apart.

2. SSR: server side rendering for dynamic content

Server side rendering in Nuxt means: on every HTTP request the Vue component is rendered on the server, the complete HTML is sent to the browser, and Vue then takes over reactivity in the browser through hydration. The advantage over pure client side rendering is dramatic: the browser immediately receives renderable HTML, the crawler sees complete content, and Time to First Byte and Largest Contentful Paint improve measurably. The downside: every request puts load on the server, and caching is possible at the HTTP level but is more complex than with static files.

In Nuxt 3, SSR is the default mode when ssr: true is set in nuxt.config.ts. Data fetching in the SSR context should happen exclusively via useAsyncData or useFetch, never via onMounted or reactive watchers. Why? In SSR, onMounted hooks are not executed on the server, only in the browser. Data loaded inside an onMounted hook is not present during server side rendering and produces hydration mismatches. That is one of the most common SSR mistakes in Nuxt.


// nuxt.config.ts: Global rendering mode configuration
export default defineNuxtConfig({
  ssr: true, // Default: SSR for all routes

  // Nitro server configuration for performance
  nitro: {
    compressPublicAssets: true,
    // Server-side cache for API responses
    routeRules: {
      '/api/**': { cache: { maxAge: 60 } }, // Cache API responses for 60s
    },
  },
})

// pages/product/[id].vue: SSR with useAsyncData
// Data is fetched on server before HTML is sent to browser
const route = useRoute()
const { data: product, error } = await useAsyncData(
  `product-${route.params.id}`,   // Unique cache key
  () => $fetch(`/api/products/${route.params.id}`)
)

// WRONG, only runs in browser, causes SSR mismatch:
// onMounted(() => { fetch('/api/products/' + id).then(...) })

// CORRECT, runs on server AND client, result hydrated:
// const { data } = await useFetch('/api/products/' + id)

3. SSG: static site generation for maximum performance

Static site generation in Nuxt means: at build time, all routes are rendered and output as static HTML files. No server needed, no database access at runtime, the generated files are served directly from a CDN. That produces the fastest possible Time to First Byte, since a static HTML file can be delivered from a CDN edge node in under 10ms. For content that changes rarely or never, documentation, blog posts, landing pages, SSG is the ideal choice.

Nuxt 3 with nuxi generate crawls every link in the application and renders all discovered routes. Dynamic routes have to be made known either via generateRoutes in the configuration or via the nitro:generate:init hook. The limitation of SSG: whenever content changes, a new build has to be triggered. For pages with frequently changing content, daily product price updates, current stock levels, pure SSG is impractical. This is where ISR comes into play.

4. ISR: incremental static regeneration as a compromise

Incremental Static Regeneration (ISR) in Nuxt combines the speed of static files with the freshness of SSR. A route is rendered server side on the first request, the result is cached and served directly from the cache for all subsequent requests, until the configured cache time expires. After that, the route is re-rendered on the next request and cached again. That gives a configurable trade-off between freshness and performance: a product page can be refreshed every 60 seconds without every request putting load on the server.

In Nuxt 3, ISR is configured via route rules: routeRules: { '/products/**': { isr: 60 } } states that all routes under /products/ should be cached for 60 seconds. The implementation runs through Nitro's edge cache and is compatible with most deployment platforms that support Nuxt's Nitro adapter, Vercel, Netlify, Cloudflare Pages. An important note: ISR in Nuxt 3 is not static output in the nuxi generate sense, but a server-side cache, a running Nuxt server is required.


// nuxt.config.ts: Hybrid Rendering with Route Rules
export default defineNuxtConfig({
  ssr: true,

  routeRules: {
    // Landing pages: statically pre-rendered at build time
    '/': { prerender: true },
    '/about': { prerender: true },
    '/blog/**': { isr: 3600 }, // Blog posts: revalidate every hour

    // Product pages: ISR with 60 second revalidation
    '/products/**': { isr: 60 },

    // User-specific routes: SSR, no caching
    '/account/**': { ssr: true },
    '/checkout/**': { ssr: true },

    // Dashboard: client-side only (auth required)
    '/dashboard/**': { ssr: false },

    // API routes: short cache, cors headers
    '/api/public/**': {
      cache: { maxAge: 30 },
      cors: true,
      headers: { 'cache-control': 's-maxage=30' },
    },
    '/api/user/**': { cache: false }, // Never cache user-specific API
  },
})

5. Hybrid rendering: different modes per route

Hybrid rendering in Nuxt 3 is the most powerful feature of the rendering system: every route can have its own rendering mode without the application having to be split apart. A Nuxt application can simultaneously run statically generated landing pages, ISR-cached product pages, SSR-rendered account pages and CSR-only dashboards, all in one codebase, one deployment. That is the fundamental difference from frameworks that commit to a single rendering mode.

Hybrid rendering in Nuxt is based on route rules, a declarative configuration in nuxt.config.ts that specifies the rendering mode, caching strategy and further request handler options for each URL path or glob pattern. Route rules are evaluated by Nitro, the server framework underlying Nuxt. This system makes it possible to serve SEO-relevant pages statically or via ISR while always rendering pages behind authentication server side.

6. Route rules: configuring rendering strategy declaratively

Route rules in Nuxt 3 are more than just rendering mode switches. They combine rendering strategy, HTTP caching headers, redirects, proxy settings and CORS configuration in a single declarative object per route. That makes the configuration transparent and maintainable: anyone who wants to know how a specific route is rendered and cached reads the route rules instead of digging through scattered middleware files.

The most important route rule options for hybrid rendering in Nuxt: prerender: true renders the route statically at build time. isr: seconds enables incremental static regeneration with the specified revalidation time. ssr: false turns off SSR for the route and serves an empty shell that the browser fills via CSR. cache: { maxAge: seconds } configures HTTP caching without ISR. redirect: '/target' sets up server-side redirects. All options can be applied per glob pattern.

7. Understanding and avoiding hydration errors

Hydration errors in Nuxt SSR occur when the server-rendered HTML does not match the HTML that Vue would render in the browser. When loading, Vue tries to "hydrate" the existing server HTML, that is, apply reactive bindings to it without re-rendering it. If the structures do not match, errors appear in the console and, in the worst case, a broken UI that only looks correct after a client re-render. That costs performance and produces layout shifts.

The most common causes of hydration errors in Nuxt are: date values that differ between server and client, for example new Date() executed in different time zones on server and client. Math.random() calls that produce different values on server and client. Browser APIs such as localStorage, window or document that are not available on the server. The pattern to avoid this: set all browser-specific values either inside onMounted or wrap them with Nuxt's <ClientOnly> wrapper, which renders the component only in the browser.


// Hydration error: Math.random() differs between server and client
// WRONG, causes mismatch:
// const id = Math.random().toString(36).slice(2)

// CORRECT, use useId() (Vue 3.5+) or generate in onMounted:
const id = useId()

// WRONG, localStorage is not available on server:
// const theme = localStorage.getItem('theme') ?? 'light'

// CORRECT, use Nuxt's useCookie or access in onMounted:
const theme = useCookie('theme', { default: () => 'light' })

// CORRECT, ClientOnly wrapper for browser-only components:
// <ClientOnly fallback-tag="div" fallback="Loading...">
//   <BrowserOnlyComponent />
// </ClientOnly>

// CORRECT, useAsyncData with server/client data parity:
const { data } = await useAsyncData('products', async () => {
  // This runs on both server (SSR) and client (navigation)
  // Same result ensures no hydration mismatch
  return $fetch('/api/products', {
    headers: useRequestHeaders(['cookie']), // Forward auth cookies
  })
})

8. Server caching with useFetch and useAsyncData

In Nuxt SSR and ISR, caching is possible on several levels. The first level is Nitro's built-in route caching via route rules. The second level is the data-fetching cache of useAsyncData and useFetch: these composables cache the result of their fetch function under the given key and, on a repeated call with the same key, return the cached result without making a new network request. That prevents duplicate fetch calls during client-side navigation and SSR rendering within the same request pipeline.

The third caching level in Nuxt SSR is the Nitro server cache with cachedFunction: arbitrary asynchronous functions, database queries for example, can be cached on the Nitro server. That gives control over caching strategy at the function level: a product lookup can be cached for 60 seconds, a user record never. This granularity makes Nuxt backends considerably more capable than naive SSR implementations that run the same database query on every request.

9. SSR vs. SSG vs. ISR vs. CSR in direct comparison

Criterion SSR SSG ISR CSR
Server required? Yes No (CDN) Yes No (CDN)
Freshness Immediate Only after build Configurable Immediate (API)
SEO Very good Very good Very good Poor
TTFB Medium Very fast Fast (cache) Slow (JS)
User-specific Yes No No Yes
Nuxt route rule ssr: true prerender: true isr: N ssr: false

The conclusion for hybrid rendering in Nuxt is clear: there is no universally best rendering mode. Static landing pages and blog posts benefit from SSG. Product pages with frequently changing prices fit perfectly with ISR at a short revalidation time. User profiles and shopping carts need SSR. Highly interactive dashboards behind authentication can run as CSR-only to take load off the server. Nuxt 3 with route rules allows all four modes to be combined within a single application.

Mironsoft

Nuxt.js SSR/SSG development, performance optimization and deployment infrastructure

Need a Nuxt application with optimal rendering strategies?

We configure SSR, SSG, ISR and hybrid rendering in Nuxt 3, with route rules, caching strategies, hydration error prevention and deployment on your target infrastructure.

Rendering audit

Analysis of existing Nuxt configurations for suboptimal rendering modes and hydration errors

Route rules configuration

Hybrid rendering strategy with ISR, SSR and static prerendering per route

Performance optimization

Server caching with cachedFunction, TTFB optimization and CDN integration

10. Summary

SSR, SSG and hybrid rendering in Nuxt 3 are not an academic concept but direct performance and SEO levers. SSR renders on the server for every request, ideal for user-specific, highly dynamic content. SSG renders at build time, ideal for static content with maximum delivery speed. ISR combines both worlds with a configurable revalidation time. CSR does not put load on the server and makes sense for auth-protected, highly interactive interfaces.

The hybrid rendering of Nuxt 3 via route rules is the decisive advantage over frameworks with a single rendering strategy. Avoiding hydration errors means: no Math.random() or new Date() without server-side consistent values, no browser API access in the SSR context without a <ClientOnly> wrapper, data fetching always via useFetch or useAsyncData instead of onMounted.

SSR, SSG and hybrid rendering in Nuxt, the essentials at a glance

SSR

Server renders on every request. Data fetching via useFetch/useAsyncData, not onMounted. Ideal for user-specific content.

SSG / ISR

SSG: static at build time. ISR: cached server side, re-rendered after a configured time. For pages with rarely to moderately changing content.

Hybrid rendering

Route rules in nuxt.config.ts: prerender, isr, ssr, cache per glob pattern. One codebase, multiple rendering modes.

Hydration

No Math.random(), new Date() or browser APIs in the SSR context. ClientOnly wrapper for browser-specific components.

11. FAQ: SSR, SSG and hybrid rendering in Nuxt

1SSR vs. SSG in Nuxt?
SSR renders on every request. SSG once at build time. SSR is fresher, SSG faster and deployable serverless.
2What is ISR in Nuxt?
Incremental Static Regeneration: the route is cached and re-rendered after a configured time. A compromise between SSR freshness and SSG speed.
3What are route rules?
Declarative configuration in nuxt.config.ts for rendering mode, caching and headers per URL path. Basis for hybrid rendering.
4Why hydration errors in Nuxt?
HTML differs between server and client. Causes: Math.random(), new Date(), browser APIs, data fetching in onMounted.
5Avoid browser API errors in SSR?
<ClientOnly> for browser-specific components. useCookie instead of localStorage. window/document only in onMounted.
6When ssr: false (CSR) in Nuxt?
For auth-protected dashboards that are never crawled and load user-specific data via API. Takes load off the server.
7useFetch in SSR vs. CSR?
SSR: runs on the server, result in the HTML payload. Navigation: runs in the browser. No duplicate request on hydration.
8Nuxt SSR without a Node.js server?
prerender: true for static pages. For actual SSR: Cloudflare Workers, Vercel Edge, Netlify possible via the Nitro adapter.
9Hybrid rendering vs. pure SSR?
Deliver static pages via CDN, dynamic ones via SSR. Lower infrastructure costs and better performance, optimally configured per route.
10cachedFunction in Nuxt server routes?
Wraps an async function with a Nitro cache: cachedFunction(fetchFn, { maxAge: 60, name: 'key' }). Prevents repeated DB queries for the same data.