# Nuxt 3: SSR and Static Generation Complete Guide > Master SSR and static generation with Nuxt 3. From useFetch to route rules, learn how to optimize performance for your Vue.js applications. - Published: 2026-02-11 - Updated: 2026-03-31 - Author: SharpSkill - Tags: nuxt 3, vue.js, ssr, static generation, web performance - Reading time: 12 min --- Nuxt 3 transforms how Vue.js applications are built by offering multiple rendering modes suited to different use cases. From Server-Side Rendering (SSR) to static generation and hybrid rendering, the framework provides remarkable flexibility for optimizing performance and SEO. > **Prerequisites** > > This tutorial assumes basic knowledge of Vue 3 and the Composition API. Familiarity with server-side rendering concepts is helpful but not required, as fundamentals are explained throughout the guide. ## Understanding Nuxt 3 Rendering Modes Before diving into code, understanding the differences between available rendering modes is essential. Each mode addresses specific needs in terms of performance, SEO, and user experience. SSR (Server-Side Rendering) generates HTML on the server for each request. Static generation (SSG) pre-generates all pages at build time. Hybrid mode allows combining these approaches on a per-page basis. ```typescript // nuxt.config.ts // Configuration for different rendering modes export default defineNuxtConfig({ // SSR enabled by default (recommended for SEO) ssr: true, // Static generation: pre-renders all pages // Use 'npm run generate' to build // target: 'static', // Nuxt 2 syntax // Hybrid mode: configurable per route routeRules: { // Homepage: pre-rendered and cached '/': { prerender: true }, // Blog: static generation '/blog/**': { prerender: true }, // Dashboard: client-side rendering only '/dashboard/**': { ssr: false }, // API: no pre-rendering '/api/**': { prerender: false } } }) ``` This configuration demonstrates the power of hybrid mode: each section of the application uses the rendering mode best suited to its needs. ## Data Fetching with useFetch and useAsyncData Nuxt 3 provides two main composables for fetching data isomorphically. These composables work both server-side and client-side, with automatic hydration management. `useFetch` is a wrapper around `useAsyncData` that simplifies HTTP calls. `useAsyncData` offers more control for advanced use cases. ```vue ``` For cases requiring more control, `useAsyncData` allows executing any asynchronous function. ```vue ``` These composables prevent double-fetching: data retrieved on the server is serialized into the HTML payload and reused during client-side hydration. ## Customizing SSR with Server Hooks Nuxt 3's SSR can be customized through server hooks. These hooks allow intervention at different stages of the rendering cycle to modify default behavior. ```typescript // server/plugins/render-hooks.ts // Server plugin to customize SSR rendering export default defineNitroPlugin((nitroApp) => { // Hook executed before rendering each page nitroApp.hooks.hook('render:html', (html, { event }) => { // Inject scripts or metadata html.head.push(` `) }) // Hook for render cache management nitroApp.hooks.hook('render:response', (response, { event }) => { // Add custom cache headers const path = event.path if (path.startsWith('/blog/')) { // Long cache for blog articles response.headers['Cache-Control'] = 'public, max-age=3600, s-maxage=86400' } else if (path.startsWith('/api/')) { // No cache for APIs response.headers['Cache-Control'] = 'no-store' } }) }) ``` > **SSR Performance** > > The `render:response` hook is ideal for implementing HTTP cache strategies. Combining SSR with a CDN that respects Cache-Control headers enables serving pre-rendered pages while maintaining the ability to invalidate them. ## Static Generation with nuxt generate Static generation pre-builds all pages at build time. This approach is ideal for sites with stable content like blogs, documentation, or marketing sites. For dynamic routes, Nuxt needs to know all URLs to generate. The `prerender:routes` hook allows defining these routes programmatically. ```typescript // nuxt.config.ts // Complete configuration for static generation export default defineNuxtConfig({ // Enable static generation nitro: { prerender: { // Enable automatic link crawling crawlLinks: true, // Routes to always include routes: ['/', '/about', '/contact'], // Ignore certain routes ignore: ['/admin', '/api'] } }, hooks: { // Hook to generate dynamic routes async 'prerender:routes'(ctx) { // Fetch articles from API or DB const articles = await fetch('https://api.example.com/articles') .then(res => res.json()) // Add article routes for (const article of articles) { ctx.routes.add(`/blog/${article.slug}`) } // Fetch categories const categories = await fetch('https://api.example.com/categories') .then(res => res.json()) for (const category of categories) { ctx.routes.add(`/category/${category.slug}`) } } } }) ``` For projects with many pages, the automatic crawler may be insufficient. Here's a more robust approach with a separate configuration file. ```typescript // server/utils/generate-routes.ts // Utility to generate dynamic route list import { prisma } from './prisma' export async function getAllStaticRoutes(): Promise { const routes: string[] = [] // Blog articles const articles = await prisma.article.findMany({ where: { published: true }, select: { slug: true, category: { select: { slug: true } } } }) for (const article of articles) { routes.push(`/blog/${article.category.slug}/${article.slug}`) } // Product pages const products = await prisma.product.findMany({ where: { active: true }, select: { slug: true } }) for (const product of products) { routes.push(`/products/${product.slug}`) } // Tag pages const tags = await prisma.tag.findMany({ select: { slug: true } }) for (const tag of tags) { routes.push(`/tags/${tag.slug}`) } return routes } ``` ## Hybrid Rendering with routeRules Hybrid rendering is Nuxt 3's flagship feature. It allows defining different rendering rules per route, combining the best of SSR and SSG. ```typescript // nuxt.config.ts // Advanced hybrid rendering configuration export default defineNuxtConfig({ routeRules: { // Marketing pages: pre-rendered and cached long-term '/': { prerender: true }, '/pricing': { prerender: true }, '/features/**': { prerender: true }, // Blog: ISR (Incremental Static Regeneration) // Revalidation every hour '/blog/**': { isr: 3600, prerender: true }, // Documentation: CDN cache with revalidation '/docs/**': { swr: 86400, // Stale-while-revalidate prerender: true }, // E-commerce: SSR with short cache '/products/**': { ssr: true, cache: { maxAge: 60, staleMaxAge: 300 } }, // Cart and checkout: client-side only '/cart': { ssr: false }, '/checkout/**': { ssr: false }, // Dashboard: SPA mode '/dashboard/**': { ssr: false, // Disable prerendering prerender: false }, // API routes: no cache by default '/api/**': { cors: true, headers: { 'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE' } } } }) ``` This configuration illustrates a typical modern application architecture: public pages are optimized for SEO with SSG, while interactive sections use client rendering. ## Performance Optimization with Data Caching Beyond page caching, Nuxt 3 allows caching fetched data. This strategy reduces API load and improves response times. ```typescript // server/api/articles/[slug].get.ts // API endpoint with data caching import { getArticleBySlug } from '~/server/utils/articles' export default defineCachedEventHandler( async (event) => { const slug = getRouterParam(event, 'slug') if (!slug) { throw createError({ statusCode: 400, message: 'Missing slug' }) } const article = await getArticleBySlug(slug) if (!article) { throw createError({ statusCode: 404, message: 'Article not found' }) } return article }, { // Cache key based on slug getKey: (event) => `article-${getRouterParam(event, 'slug')}`, // Cache duration: 1 hour maxAge: 3600, // Stale-while-revalidate: serve stale cache during update staleMaxAge: 7200, // Tag-based invalidation tags: ['articles'] } ) ``` To invalidate cache when content changes, Nuxt provides a tag system. ```typescript // server/api/articles/[slug].put.ts // Article update with cache invalidation import { updateArticle } from '~/server/utils/articles' export default defineEventHandler(async (event) => { const slug = getRouterParam(event, 'slug') const body = await readBody(event) // Update the article const article = await updateArticle(slug, body) // Invalidate cache for this article await useStorage('cache').removeItem(`nitro:handlers:article-${slug}`) // Or tag-based invalidation (all articles) // await useStorage('cache').clear('articles') return article }) ``` > **Distributed Cache** > > In production with multiple instances, in-memory cache is insufficient. Configuring Redis or another distributed system via Nitro configuration is recommended to ensure consistency across instances. ## SEO and Metadata Management SSR enables SEO optimization by generating metadata server-side. Nuxt 3 offers several approaches for managing meta tags dynamically. ```vue ``` For static pages, metadata can be defined directly in the component. ```vue ``` ## Deployment and Production Considerations The deployment choice depends on the rendering mode used. Here are the main options and their configurations. ```typescript // nuxt.config.ts // Configuration for different deployment environments export default defineNuxtConfig({ nitro: { // Preset based on target platform // preset: 'vercel', // Vercel // preset: 'netlify', // Netlify // preset: 'cloudflare-pages', // Cloudflare // preset: 'node-server', // Classic Node.js // Configuration for Node.js in production preset: 'node-server', // Response compression compressPublicAssets: true, // Cache storage configuration storage: { cache: { driver: 'redis', url: process.env.REDIS_URL } } }, // Runtime environment variables runtimeConfig: { // Secrets (not exposed to client) apiSecret: process.env.API_SECRET, // Public configuration public: { apiBase: process.env.NUXT_PUBLIC_API_BASE || '/api' } } }) ``` For static deployment, the `npm run generate` command creates an `.output/public` folder ready to deploy on any static file host. ```bash # Static generation npm run generate # The .output/public content can be deployed to: # - Vercel (automatic detection) # - Netlify (automatic configuration) # - GitHub Pages # - S3 + CloudFront # - Any CDN or static file server ``` ## Conclusion Nuxt 3 offers exceptional flexibility for rendering Vue.js applications. The choice between SSR, SSG, and hybrid rendering depends on each project's specific needs. **Key takeaways:** ✅ **SSR**: ideal for dynamic content requiring good SEO (e-commerce, news sites) ✅ **SSG**: perfect for stable content (blogs, documentation, marketing sites) ✅ **Hybrid**: the best approach for complex applications with varied needs ✅ **useFetch/useAsyncData**: automatic hydration and cache management ✅ **routeRules**: fine-grained configuration for each route's behavior ✅ **Caching**: multiple strategies to optimize production performance Combining hybrid rendering with a well-thought-out caching strategy enables building performant applications optimized for SEO while maintaining the interactivity of Single Page Applications. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/en/blog/vue-nuxt/nuxt-3-ssr-static-generation