Your marketing pages need instant loading (SSG). Your product catalog needs fresh data (SSR). Your blog needs the best of both (ISR). In 2024, you had to choose one. In 2025, Nuxt 3 lets you use all three in the same application.
Google's crawlers treat SSR and SSG pages differently. Choose wrong, and you're leaving rankings on the table. Choose right, and you outperform competitors using Next.js or vanilla Vue.
The problem with traditional web frameworks is simple. They force you to pick one rendering strategy for your entire application. You build a static site and watch product data go stale. You build a server-rendered app and pay for server time on pages that never change. You build a single-page application and watch your SEO rankings disappear.
Nuxt 3 hybrid rendering solves this problem. You can use server-side rendering for your dashboard, static generation for your homepage, incremental static regeneration for your blog, and client-side rendering for your admin panel. All in the same codebase. All configured in one file.
This guide teaches you when to use SSR, SSG, ISR, and SPA. You'll learn how to configure route rules for maximum SEO performance. You'll see real examples from e-commerce sites, blogs, and SaaS applications. You'll understand how to make Google love your site while keeping hosting costs low.
In July 2025, Vercel acquired NuxtLabs. This acquisition brought more resources to Nuxt development. The framework is now positioned to dominate Vue-based SSR for the next 3-5 years. Learning Nuxt 3 hybrid rendering now gives you a competitive advantage as the ecosystem matures.
Let's start with the basics.
Understanding Nuxt 3 Rendering Modes
Nuxt 3 offers four distinct rendering strategies. Each one generates HTML differently. Each one serves different needs. Each one impacts SEO in unique ways.
The Four Rendering Strategies
Here's how the four modes compare:
| Feature | SSR | SSG | ISR | SPA |
|---|---|---|---|---|
| HTML Generation | On each request | At build time | On-demand with cache | In browser only |
| Best For | Dynamic content, personalized pages | Marketing pages, docs | Product pages, blog posts | Admin panels, dashboards |
| SEO Impact | Excellent (fresh content) | Excellent (instant load) | Excellent (fresh + fast) | Poor (needs JS crawling) |
| Server Load | High (every request) | None (static files) | Low (cache hits) | None (client-side only) |
| Content Freshness | Always current | Stale until rebuild | Stale for TTL period | Real-time (if API used) |
| Core Web Vitals | Good (with caching) | Excellent | Excellent | Variable |
| Infrastructure | Requires Node.js server | Static hosting (CDN) | Node.js with caching | Static hosting (CDN) |
Let me explain each rendering mode in detail.
Server-Side Rendering (SSR) generates HTML on your server for every request. When a user visits your page, Nuxt runs your Vue components on the server. It creates HTML. It sends that HTML to the browser. The browser displays it immediately.
SSR works best for dynamic content. User dashboards, shopping carts, personalized recommendations, and real-time data displays all benefit from SSR. The content is always fresh. Crawlers see complete HTML. SEO performs well.
The trade-off is server load. Every page view requires server processing. You need a Node.js server running 24/7. Hosting costs scale with traffic. You need caching strategies to manage load.
Static Site Generation (SSG) generates HTML at build time. When you run npm run build, Nuxt pre-renders every route. It creates HTML files. You deploy those files to a CDN. Users get instant responses.
SSG works best for content that rarely changes. Marketing pages, documentation, blog posts, and landing pages are perfect candidates. The pages load instantly. Core Web Vitals scores are perfect. Hosting costs are minimal.
The trade-off is staleness. Content stays frozen until your next build. Adding a blog post requires rebuilding and redeploying. Sites with thousands of pages face long build times.
Incremental Static Regeneration (ISR) combines SSR and SSG benefits. Pages generate on-demand and cache for a specified time. The first visitor triggers generation. Nuxt caches the result. Subsequent visitors get the cached version. After the cache expires, Nuxt regenerates the page.
ISR works best for frequently updated content with acceptable staleness. E-commerce product pages, news articles, and blog posts benefit from ISR. Content stays reasonably fresh. Server load stays low. SEO remains excellent.
The trade-off is complexity. The first visitor waits longer. Cache invalidation requires planning. You need to understand TTL strategies.
Single Page Application (SPA) renders content in the browser with JavaScript. The server sends a minimal HTML shell. JavaScript loads. It fetches data. It renders the interface.
SPA works best for authenticated areas. Admin panels, user dashboards, and interactive tools don't need SEO. Fast navigation after initial load improves user experience.
The trade-off is SEO. Crawlers must execute JavaScript to see content. Initial load takes longer. Without server rendering, you lose search visibility.
The magic of Nuxt 3 isn't any single mode. It's using the right mode for each route. Your homepage doesn't need the same rendering as your admin panel.
What Is Hybrid Rendering and Why It Matters
Nuxt 3 hybrid rendering means using different rendering modes on different routes in the same application. Your marketing pages use SSG. Your product pages use ISR. Your user dashboard uses SSR. Your admin panel uses SPA.
The Evolution from Monolithic to Hybrid
Nuxt 2 forced you to choose SSR or SSG for your entire app. You configured target: 'static' or target: 'server' in your config file. Every route used the same rendering strategy.
This created problems. An e-commerce site needs static marketing pages, fresh product data, and dynamic user accounts. Choosing one mode meant compromising somewhere. Static sites served stale product data. Server-rendered sites wasted CPU on unchanging pages.
Nuxt 3 solved this with Nitro. Nitro is Nuxt's server engine. It enables per-route rendering strategies. You configure route rules in nuxt.config.ts. Nitro handles the rest.
Nitro also provides deployment flexibility. The same codebase deploys to Vercel, Netlify, Cloudflare Workers, AWS Lambda, or a traditional Node.js server. Nitro adapts your app to each platform automatically.
SEO Benefits of Hybrid Rendering
Hybrid rendering delivers specific SEO advantages that monolithic rendering can't match.
Optimal crawl budget usage means search engines spend time wisely. Static pages serve from CDN instantly. Crawlers don't waste your server resources. Dynamic pages get server resources when needed. You maximize crawler efficiency.
Perfect Core Web Vitals per page type means each route optimizes differently. Marketing pages use SSG for instant LCP (Largest Contentful Paint). Product pages use ISR for balanced speed and freshness. User pages use SSR for personalization. Each page type achieves optimal performance metrics.
Strategic caching keeps content fresh without constant rebuilds. ISR updates product pages every 10 minutes. Blog posts regenerate hourly. Marketing pages stay static for months. You control freshness per route.
Crawl efficiency improves when search engines get instant responses. Static routes return immediately. Cached ISR routes return quickly. Search engines can crawl more pages per session. Your entire site gets indexed faster.
Sites using Nuxt 3 hybrid rendering see 25-40% better Core Web Vitals scores compared to pure SSR. They maintain content freshness that pure SSG can't match. This combination improves rankings and user experience.
Implementing Hybrid Rendering with Route Rules
Route rules are the foundation of Nuxt 3 hybrid rendering. You configure them in nuxt.config.ts. Nitro reads these rules and applies the correct rendering strategy to each route.
Understanding Route Rules in nuxt.config.ts
Route rules use a simple pattern-matching syntax. You specify a route pattern and configuration object. Nuxt applies those settings to matching routes.
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
// Static homepage
'/': { prerender: true },
// ISR for blog with 1-hour cache
'/blog/**': { isr: 3600 },
// Stale-while-revalidate for products
'/products/**': { swr: true },
// Enable CORS for API routes
'/api/**': { cors: true },
// Client-side rendering for admin
'/admin/**': { ssr: false }
}
})
The pattern syntax uses wildcards. Single asterisk (*) matches one segment. Double asterisk (**) matches multiple segments. You can target exact routes or entire sections of your site.
Each route rule accepts different options. Let's explore each rendering mode in detail.
SSG with Prerendering
Prerendering generates static HTML files at build time. You mark routes with prerender: true. During npm run build, Nuxt crawls these routes and creates HTML files.
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
// Individual static pages
'/': { prerender: true },
'/about': { prerender: true },
'/contact': { prerender: true },
'/privacy': { prerender: true },
'/terms': { prerender: true },
// All blog posts (crawler will follow links)
'/blog/**': { prerender: true },
// All documentation pages
'/docs/**': { prerender: true },
// Landing pages
'/features': { prerender: true },
'/pricing': { prerender: true }
},
// Optional: Explicitly list routes if crawler can't find them
nitro: {
prerender: {
routes: [
'/',
'/about',
'/blog/post-1',
'/blog/post-2'
]
}
}
})
Nuxt's crawler starts with your listed routes. It follows internal links to discover more pages. You can also explicitly list routes in nitro.prerender.routes if the crawler can't find them.
When to use SSG:
- Marketing pages that change monthly or less
- Homepage and landing pages
- About, contact, and legal pages
- Documentation that updates with releases
- Blog posts (if you don't need immediate updates)
- Portfolio and showcase pages
SSG delivers the best possible performance. Files serve from CDN edge locations worldwide. Time to First Byte (TTFB) is under 50ms. Largest Contentful Paint (LCP) happens in milliseconds. Core Web Vitals scores are perfect.
The limitation is staleness. Content freezes until the next build and deployment. Adding a blog post requires rebuilding your site. For content that changes frequently, use ISR instead.
SSR with Dynamic Routes
Server-side rendering generates HTML on every request. You enable SSR by default or explicitly with ssr: true. You can add cache headers to improve performance.
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
// User dashboard - always fresh, no caching
'/dashboard/**': {
ssr: true,
headers: {
'cache-control': 'private, no-cache, no-store, must-revalidate'
}
},
// Shopping cart - fresh but cacheable for 30 seconds
'/cart': {
ssr: true,
headers: {
'cache-control': 'private, max-age=30'
}
},
// Search results - cache for 5 minutes
'/search': {
ssr: true,
headers: {
'cache-control': 'public, max-age=300, s-maxage=300'
}
},
// User profiles - cache but revalidate
'/users/:id': {
ssr: true,
headers: {
'cache-control': 'public, max-age=600, must-revalidate'
}
}
}
})
Cache headers control browser and CDN caching. private means only the user's browser can cache. public means CDNs can cache. max-age controls browser cache duration. s-maxage controls CDN cache duration.
When to use SSR:
- User dashboards showing personal data
- Shopping carts and checkout flows
- Search results pages
- Personalized recommendations
- Real-time inventory displays
- User-generated content feeds
- Any page requiring authentication
- Pages with content that changes per user
SSR ensures content freshness. Users always see current data. Crawlers get complete HTML. SEO performs well.
The limitation is server load. Every page view requires server processing. You need good caching strategies. Monitor server resources carefully.
ISR (Incremental Static Regeneration)
Incremental static regeneration generates HTML on-demand and caches it with a time-to-live (TTL). The first visitor triggers generation. Nuxt caches the result. Subsequent visitors get the cached version. After TTL expires, the next visitor triggers regeneration.
Nuxt 3 offers two ISR approaches: time-based ISR and stale-while-revalidate (SWR).
Time-based ISR caches content for a specific duration. After expiration, Nuxt regenerates on the next request.
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
// Blog posts - regenerate every hour
'/blog/**': {
isr: 3600, // 3600 seconds = 1 hour
headers: {
'cache-control': 'public, max-age=3600, s-maxage=3600'
}
},
// Product pages - regenerate every 10 minutes
'/products/:id': {
isr: 600, // 600 seconds = 10 minutes
headers: {
'cache-control': 'public, max-age=600, s-maxage=600'
}
},
// Category pages - regenerate every 5 minutes
'/category/**': {
isr: 300, // 300 seconds = 5 minutes
headers: {
'cache-control': 'public, max-age=300, s-maxage=300'
}
},
// News articles - regenerate every 30 minutes
'/news/**': {
isr: 1800, // 1800 seconds = 30 minutes
headers: {
'cache-control': 'public, max-age=1800, s-maxage=1800'
}
}
}
})
The TTL value (in seconds) determines cache duration. Choose based on how fresh content needs to be. Product inventory might need 5-minute caches. Blog posts might tolerate 1-hour caches.
Stale-While-Revalidate (SWR) serves cached content immediately and updates in the background. Users always get instant responses. Nuxt updates the cache asynchronously.
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
// News feed - serve stale content, update in background
'/news/**': {
swr: true,
swr: 60 // Revalidate every 60 seconds
},
// Product listings - instant response, background updates
'/products': {
swr: 120 // Revalidate every 2 minutes
},
// Category pages - fast browsing experience
'/category/**': {
swr: 180 // Revalidate every 3 minutes
}
}
})
The difference between ISR and SWR is user experience. ISR makes the first post-expiration visitor wait for regeneration. SWR serves stale content immediately and updates the cache for the next visitor.
When to use ISR:
- Blog posts that update occasionally
- E-commerce product pages
- News articles
- Documentation that updates regularly
- User-generated content (forums, comments)
- API responses with tolerable staleness
- Any content between "completely static" and "always fresh"
ISR balances freshness and performance. You get near-static performance with regular updates. Server load stays low because of caching. SEO benefits from both speed and fresh content.
The limitation is the first visitor after expiration. That visitor waits for regeneration. Use SWR if this is unacceptable.
SPA Mode for Client-Only Routes
Single-page application mode disables server rendering. Nuxt sends minimal HTML. JavaScript handles rendering in the browser.
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
// Admin panel - no SSR needed
'/admin/**': {
ssr: false,
headers: {
'x-robots-tag': 'noindex, nofollow' // Don't index admin pages
}
},
// User settings - authenticated only
'/settings/**': {
ssr: false,
headers: {
'cache-control': 'private, no-cache',
'x-robots-tag': 'noindex'
}
},
// Interactive tools - client-side only
'/tools/**': {
ssr: false
}
}
})
The x-robots-tag header tells search engines not to index these pages. You don't want admin panels in search results.
When to use SPA mode:
- Admin panels and dashboards
- User settings and account management
- Interactive tools requiring heavy client-side logic
- Pages behind authentication
- Internal applications
- Any route you don't want indexed
SPA mode reduces server load for pages that don't need SEO. Users get fast navigation after the initial JavaScript loads. You avoid rendering pages that search engines shouldn't see.
The limitation is SEO. Crawlers won't index SPA pages effectively. Only use SPA for pages where this is acceptable.
Real-World Hybrid Rendering Examples
Let me show you complete configurations for three common application types. These examples demonstrate how to combine rendering modes for optimal performance and SEO.
Example 1: E-Commerce Site Architecture
E-commerce sites have distinct needs per page type. Marketing pages should load instantly. Product pages need fresh inventory data. Category pages need good UX. Checkout must always be current.
// nuxt.config.ts for an e-commerce site
export default defineNuxtConfig({
routeRules: {
// Marketing pages - fully static for instant loading
'/': { prerender: true },
'/about': { prerender: true },
'/shipping': { prerender: true },
'/returns': { prerender: true },
'/privacy': { prerender: true },
'/terms': { prerender: true },
// Product pages - ISR for fresh inventory without server load
// Regenerate every 5 minutes to show current stock
'/products/:id': {
isr: 300,
headers: {
'cache-control': 'public, max-age=300, s-maxage=600'
}
},
// Product listings - ISR with longer cache
// Category pages change less frequently than individual products
'/products': {
isr: 600,
headers: {
'cache-control': 'public, max-age=600, s-maxage=1200'
}
},
// Category pages - SWR for instant browsing
// Users expect fast category navigation
'/category/**': {
swr: 120
},
// Search results - short SSR cache
// Results change based on inventory and relevance
'/search': {
ssr: true,
headers: {
'cache-control': 'public, max-age=180, s-maxage=180'
}
},
// Blog - static with ISR for new posts
// Update hourly to show new content without rebuilds
'/blog': { isr: 3600 },
'/blog/**': { isr: 3600 },
// User account pages - SSR with private caching
// Personal data must be fresh and private
'/account/**': {
ssr: true,
headers: {
'cache-control': 'private, max-age=0, must-revalidate'
}
},
// Shopping cart - SSR without caching
// Cart contents must always be current
'/cart': {
ssr: true,
headers: {
'cache-control': 'private, no-cache, no-store, must-revalidate'
}
},
// Checkout process - SSR without caching
// Payment and order data must be perfectly accurate
'/checkout/**': {
ssr: true,
headers: {
'cache-control': 'private, no-cache, no-store, must-revalidate',
'x-frame-options': 'DENY' // Security for payment pages
}
},
// Wishlist - SSR with short cache
// User-specific but can tolerate brief staleness
'/wishlist': {
ssr: true,
headers: {
'cache-control': 'private, max-age=60'
}
},
// Order history - SSR with longer cache
// Past orders rarely change
'/orders/**': {
ssr: true,
headers: {
'cache-control': 'private, max-age=300'
}
},
// API routes - enable CORS
'/api/**': {
cors: true,
headers: {
'cache-control': 'private, max-age=0'
}
}
}
})
This configuration optimizes each section differently:
Marketing pages use full static generation. They load instantly from CDN. Perfect Core Web Vitals scores improve SEO. These pages drive search traffic and conversions.
Product pages use 5-minute ISR. Inventory updates show quickly without constant regeneration. The first visitor after expiration waits slightly longer. Subsequent visitors get cached HTML. This balances freshness with performance.
Category pages use SWR with 2-minute revalidation. Users get instant responses. Cache updates happen in the background. Browsing feels fast and responsive.
Checkout and cart use SSR without caching. Data must be perfectly current. No user should see stale cart totals or outdated order status. Security is critical here.
Blog posts use 1-hour ISR. Content freshness matters less than product data. Longer caches reduce server load while keeping content reasonably current.
Moving 10 marketing pages from SSR to SSG can reduce server costs by 60%. Those pages serve from CDN instead of hitting your server. Scaling becomes cheaper.
Example 2: Content/Blog Site Architecture
Content sites prioritize reading experience and SEO. Most pages can use static generation or ISR. Server rendering is rarely needed.
// nuxt.config.ts for a blog/content site
export default defineNuxtConfig({
routeRules: {
// Homepage - static with ISR for new posts
'/': {
isr: 1800, // 30 minutes
headers: {
'cache-control': 'public, max-age=1800, s-maxage=3600'
}
},
// About and static pages - fully static
'/about': { prerender: true },
'/contact': { prerender: true },
'/privacy': { prerender: true },
// Blog posts - ISR for updated content
// Authors may fix typos or update information
'/blog/**': {
isr: 3600, // 1 hour
headers: {
'cache-control': 'public, max-age=3600, s-maxage=7200'
}
},
// Category/tag pages - ISR for new posts
'/category/**': { isr: 1800 },
'/tag/**': { isr: 1800 },
// Author pages - ISR
'/author/**': { isr: 3600 },
// Newsletter signup - SSR
'/newsletter': {
ssr: true,
headers: {
'cache-control': 'public, max-age=600'
}
},
// Search - SSR with short cache
'/search': {
ssr: true,
headers: {
'cache-control': 'public, max-age=300'
}
}
}
})
Content sites benefit heavily from ISR. Posts rarely change after publication. ISR provides the speed of static generation with the flexibility to update content without rebuilds.
Example 3: SaaS Application Architecture
SaaS applications divide into public marketing and private application areas. Marketing needs SEO. The application needs freshness and personalization.
// nuxt.config.ts for a SaaS application
export default defineNuxtConfig({
routeRules: {
// Public marketing - fully static
'/': { prerender: true },
'/features': { prerender: true },
'/pricing': { prerender: true },
'/about': { prerender: true },
'/contact': { prerender: true },
// Documentation - static with ISR for updates
'/docs/**': {
isr: 7200, // 2 hours
headers: {
'cache-control': 'public, max-age=7200'
}
},
// Blog - ISR
'/blog/**': { isr: 3600 },
// Changelog - ISR for new releases
'/changelog': { isr: 1800 },
// Login/signup pages - SSR with short cache
'/login': {
ssr: true,
headers: {
'cache-control': 'public, max-age=300'
}
},
'/signup': {
ssr: true,
headers: {
'cache-control': 'public, max-age=300'
}
},
// Application dashboard - SPA mode
// Authenticated users, no SEO needed, rich interactivity
'/app/**': {
ssr: false,
headers: {
'cache-control': 'private, no-cache',
'x-robots-tag': 'noindex, nofollow'
}
},
// User settings - SPA mode
'/settings/**': {
ssr: false,
headers: {
'cache-control': 'private, no-cache',
'x-robots-tag': 'noindex'
}
},
// API routes - no SSR, enable CORS
'/api/**': {
ssr: false,
cors: true
}
}
})
SaaS architecture splits cleanly. Public pages use static generation for SEO and performance. The application uses SPA mode for rich interactivity. Documentation uses ISR to update without rebuilds.
This approach gives you the best of both worlds. Marketing pages rank well and load fast. The application feels responsive and dynamic.
SEO Optimization with Hybrid Rendering
Hybrid rendering enables powerful SEO strategies. You can optimize meta tags, sitemaps, canonical URLs, and structured data per rendering mode.
Meta Tags and Dynamic SEO
The useSeoMeta composable sets meta tags for each page. With SSR, SSG, or ISR, these tags render on the server. Crawlers see them immediately.
// pages/blog/[slug].vue
<script setup>
const route = useRoute()
// Fetch article data
const { data: article } = await useFetch(`/api/blog/${route.params.slug}`)
// Set SEO meta tags
useSeoMeta({
title: article.value.title,
description: article.value.excerpt,
ogTitle: article.value.title,
ogDescription: article.value.excerpt,
ogImage: article.value.coverImage,
ogType: 'article',
twitterCard: 'summary_large_image',
twitterTitle: article.value.title,
twitterDescription: article.value.excerpt,
twitterImage: article.value.coverImage
})
// Set article-specific meta
useHead({
meta: [
{ property: 'article:published_time', content: article.value.publishedAt },
{ property: 'article:modified_time', content: article.value.updatedAt },
{ property: 'article:author', content: article.value.author },
{ property: 'article:section', content: article.value.category }
]
})
</script>
<template>
<article>
<h1>{{ article.title }}</h1>
<div v-html="article.content"></div>
</article>
</template>
With ISR or SSG, this code runs at build or regeneration time. The resulting HTML contains complete meta tags. Crawlers don't need JavaScript. They see perfect SEO data immediately.
With SPA mode, these tags render in the browser. Crawlers must execute JavaScript. SEO suffers. Only use SPA for pages you don't want indexed.
Sitemaps and Route Rules
The @nuxtjs/seo module generates sitemaps automatically. It respects your route rules.
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxtjs/seo'],
site: {
url: 'https://example.com',
name: 'My Site'
},
sitemap: {
strictNuxtContentPaths: true,
exclude: [
'/admin/**',
'/app/**',
'/settings/**'
]
},
routeRules: {
// Prerendered pages appear in sitemap
'/': { prerender: true },
'/about': { prerender: true },
// ISR pages appear in sitemap
'/blog/**': { isr: 3600 },
// SPA pages excluded from sitemap
'/admin/**': { ssr: false }
}
})
The sitemap includes prerendered and ISR routes. It excludes SPA routes. Search engines discover your important pages and ignore private areas.
Canonical URLs and Prerender Links
Canonical URLs tell search engines which version of a page is authoritative. This prevents duplicate content issues.
// pages/products/[id].vue
<script setup>
const route = useRoute()
const config = useRuntimeConfig()
// Set canonical URL
useHead({
link: [
{
rel: 'canonical',
href: `${config.public.siteUrl}/products/${route.params.id}`
}
]
})
</script>
With ISR or SSG, the canonical URL renders in HTML. Crawlers see it immediately. Your SEO stays clean.
Structured Data with Hybrid Rendering
Structured data helps search engines understand your content. JSON-LD format works well with Nuxt 3.
// pages/blog/[slug].vue
<script setup>
const route = useRoute()
const config = useRuntimeConfig()
const { data: article } = await useFetch(`/api/blog/${route.params.slug}`)
// Create Article structured data
const structuredData = {
'@context': 'https://schema.org',
'@type': 'Article',
headline: article.value.title,
description: article.value.excerpt,
image: article.value.coverImage,
datePublished: article.value.publishedAt,
dateModified: article.value.updatedAt,
author: {
'@type': 'Person',
name: article.value.author
},
publisher: {
'@type': 'Organization',
name: config.public.siteName,
logo: {
'@type': 'ImageObject',
url: `${config.public.siteUrl}/logo.png`
}
}
}
// Add structured data to head
useHead({
script: [
{
type: 'application/ld+json',
children: JSON.stringify(structuredData)
}
]
})
</script>
With ISR or SSG, this structured data renders in the initial HTML. Search engines parse it immediately. Rich snippets appear in search results. Click-through rates improve.
Structured data is server-rendered with SSR, SSG, and ISR. It's immediately available to crawlers. This gives you a significant SEO advantage over SPA-only sites.
For detailed Core Web Vitals optimization, see our complete guide to optimizing Core Web Vitals in Nuxt 3.
Performance and Caching Strategies
Hybrid rendering works best with proper caching. Each rendering mode requires different cache strategies.
CDN Integration
Content Delivery Networks cache and serve your content from edge locations worldwide. Each rendering mode interacts with CDNs differently.
SSG pages are perfect for CDN caching. Files are immutable until the next build. Set long cache durations.
routeRules: {
'/': {
prerender: true,
headers: {
// Cache for 1 year - file won't change until next build
'cache-control': 'public, max-age=31536000, immutable'
}
}
}
ISR pages cache with TTL at the CDN. The CDN serves cached versions until expiration. After expiration, it fetches a fresh version from your server.
routeRules: {
'/blog/**': {
isr: 3600,
headers: {
// CDN caches for same duration as ISR
'cache-control': 'public, max-age=3600, s-maxage=3600'
}
}
}
SSR pages can use edge caching with short TTL. The CDN reduces server load for popular pages.
routeRules: {
'/search': {
ssr: true,
headers: {
// CDN caches for 5 minutes
'cache-control': 'public, max-age=300, s-maxage=300'
}
}
}
SPA routes serve static JavaScript bundles from CDN. The bundles themselves cache long-term. API calls handle data freshness.
routeRules: {
'/app/**': {
ssr: false,
headers: {
// JS bundle caches, but data stays fresh via API
'cache-control': 'public, max-age=31536000, immutable'
}
}
}
Cache Headers Best Practices
Different rendering modes need different cache strategies. Here's a reference table:
| Rendering Mode | Browser Cache | CDN Cache | Example Headers |
|---|---|---|---|
| SSG (Static) | Long (1 year) | Long (1 year) | public, max-age=31536000, immutable |
| ISR | Medium (match TTL) | Medium (match TTL) | public, max-age=3600, s-maxage=3600 |
| SSR (Public) | Short (5 min) | Short (5 min) | public, max-age=300, s-maxage=300 |
| SSR (Private) | None | None | private, no-cache, no-store, must-revalidate |
| SPA (Bundle) | Long (1 year) | Long (1 year) | public, max-age=31536000, immutable |
| SPA (Route) | None | N/A | no-cache, no-store |
Cache header directives explained:
public- CDNs and browsers can cacheprivate- Only user's browser can cache (not CDN)max-age=N- Browser caches for N secondss-maxage=N- CDN caches for N seconds (overrides max-age for CDN)must-revalidate- Cache must check server before serving staleno-cache- Must revalidate with server before servingno-store- Don't cache at allimmutable- Content will never change (safe to cache indefinitely)
Set headers based on your content update frequency. Static pages use long caches. Dynamic pages use short caches. Private pages don't cache.
Monitoring Rendering Performance
Monitor your hybrid rendering setup to verify it's working correctly. Use these tools and techniques.
Nitro DevTools show which rendering mode served each route. Enable dev mode and check the Nitro panel in your browser.
Chrome DevTools Network tab reveals cache status. Look for:
(disk cache)or(memory cache)- Browser served from cachecf-cache-status: HIT- Cloudflare CDN served from cachex-vercel-cache: HIT- Vercel edge served from cache- Response time - SSG and cached ISR should be under 100ms
Google Search Console shows crawl stats. Monitor:
- Crawl requests per day - Should stay stable or decrease with better caching
- Time spent downloading - Should decrease with SSG/ISR
- Core Web Vitals - Should improve with hybrid rendering
Cloudflare Analytics (or your CDN analytics) show cache hit rates. Aim for:
- 90%+ hit rate on SSG pages
- 70-90% hit rate on ISR pages
- 40-70% hit rate on cached SSR pages
If cache hit rates are low, increase TTL or switch to a more static rendering mode.
Common Pitfalls and How to Avoid Them
Developers make predictable mistakes when implementing Nuxt 3 hybrid rendering. Here's how to avoid them.
Issue 1: Over-Caching Dynamic Content
Problem: You set long ISR TTL on content that needs to be fresh. Users see stale data. Inventory shows products that sold out. Prices show old values.
Solution: Match TTL to your actual update frequency. If inventory updates every 2 minutes, use 120-second ISR. Don't guess. Measure how often data actually changes.
// Bad - 1 hour cache on frequently changing data
routeRules: {
'/products/:id': { isr: 3600 } // Product could sell out
}
// Good - 5 minute cache matches inventory update frequency
routeRules: {
'/products/:id': {
isr: 300,
headers: {
'cache-control': 'public, max-age=300, s-maxage=300'
}
}
}
You can also use SWR instead of ISR. SWR serves cached content immediately and updates in the background. Users always get fast responses.
Issue 2: Under-Caching Static Content
Problem: You use SSR for pages that never change. Marketing pages, legal pages, and about pages hit your server on every view. Server costs run high. Performance suffers.
Solution: Audit your routes. Identify pages that change monthly or less. Switch them to SSG. Your server load will drop dramatically.
Moving 10 marketing pages from SSR to SSG can reduce server costs by 60%. Those pages serve from CDN instead of hitting your server.
// Bad - SSR for static content
routeRules: {
'/about': { ssr: true },
'/contact': { ssr: true },
'/privacy': { ssr: true },
'/terms': { ssr: true }
}
// Good - SSG for static content
routeRules: {
'/about': { prerender: true },
'/contact': { prerender: true },
'/privacy': { prerender: true },
'/terms': { prerender: true }
}
Your CDN serves these pages in milliseconds. Your server handles zero load. Your costs drop.
Issue 3: Not Setting Cache Headers
Problem: You configure ISR but forget cache headers. CDNs and browsers don't cache responses. Every request hits your server. ISR provides no benefit.
Solution: Always set matching cache headers. ISR TTL and cache-control should align.
// Bad - ISR without cache headers
routeRules: {
'/blog/**': { isr: 3600 }
}
// Good - ISR with matching cache headers
routeRules: {
'/blog/**': {
isr: 3600,
headers: {
'cache-control': 'public, max-age=3600, s-maxage=3600'
}
}
}
The max-age controls browser caching. The s-maxage controls CDN caching. Match both to your ISR TTL for consistent behavior.
Issue 4: Breaking ISR with Query Parameters
Problem: Query parameters create unique cache keys. /products/123?ref=email and /products/123?ref=social cache separately. Your cache hit rate drops. Server load increases.
Solution: Normalize URLs or configure caching to ignore certain parameters.
// Option 1: Remove unnecessary query params in middleware
// middleware/normalize-url.ts
export default defineNuxtRouteMiddleware((to) => {
// Keep only essential query params
const allowedParams = ['page', 'sort']
const filtered = {}
allowedParams.forEach(key => {
if (to.query[key]) {
filtered[key] = to.query[key]
}
})
// Redirect if query params changed
if (Object.keys(to.query).length !== Object.keys(filtered).length) {
return navigateTo({
path: to.path,
query: filtered
}, { redirectCode: 301 })
}
})
// Option 2: Configure your CDN to ignore query params
// Most CDNs support this in their dashboard
// For Cloudflare Workers:
routeRules: {
'/products/**': {
isr: 300,
headers: {
'cache-control': 'public, max-age=300, s-maxage=300',
'cache-tag': 'products' // Use for cache purging
}
}
}
Reducing unnecessary query parameters improves cache efficiency. Your ISR works better.
Issue 5: Forgetting About Mobile vs Desktop
Problem: You assume responsive design means one HTML works for all devices. Some sites need different markup for mobile and desktop. Caching breaks when HTML varies.
Solution: Use responsive design when possible. If you truly need different markup, use separate routes or device detection in SSR.
// Responsive design (preferred) - one HTML for all devices
<template>
<div>
<!-- Mobile layout -->
<div class="md:hidden">
<MobileNav />
</div>
<!-- Desktop layout -->
<div class="hidden md:block">
<DesktopNav />
</div>
</div>
</template>
// If you need different markup, use SSR with device detection
// composables/useDevice.ts
export const useDevice = () => {
const headers = useRequestHeaders()
const userAgent = headers['user-agent'] || ''
return {
isMobile: /mobile/i.test(userAgent),
isTablet: /tablet|ipad/i.test(userAgent),
isDesktop: !/mobile|tablet|ipad/i.test(userAgent)
}
}
// Then in your component:
<script setup>
const { isMobile } = useDevice()
</script>
<template>
<MobileVersion v-if="isMobile" />
<DesktopVersion v-else />
</template>
Responsive design is simpler and works better with caching. Only use device detection when absolutely necessary.
Nuxt 3 vs Next.js: Hybrid Rendering Comparison
Next.js and Nuxt 3 both offer excellent hybrid rendering. The frameworks are converging in features, especially after Vercel's acquisition of NuxtLabs in July 2025.
Feature Parity in 2025
Both frameworks support the same core rendering strategies. The syntax and configuration differ, but capabilities are equivalent.
| Feature | Nuxt 3 | Next.js 14/15 |
|---|---|---|
| SSR | ✅ Universal rendering | ✅ Server Components |
| SSG | ✅ prerender: true |
✅ generateStaticParams |
| ISR | ✅ isr: TTL |
✅ revalidate: TTL |
| SWR | ✅ swr: TTL |
✅ Via cache config |
| Route-level config | ✅ routeRules in config |
✅ Route Handlers + config |
| Deployment flexibility | ✅ Deploy anywhere (Nitro) | ⚠️ Best on Vercel |
| Edge rendering | ✅ Via Nitro adapters | ✅ Edge Runtime |
| Middleware | ✅ Server + route middleware | ✅ Edge middleware |
| API routes | ✅ server/api/** |
✅ app/api/** |
| File-based routing | ✅ pages/** |
✅ app/** |
| Ecosystem | React (largest) | Vue (large) |
Key differences in approach:
Nuxt 3 configures rendering in nuxt.config.ts. Route rules use pattern matching. Configuration is centralized and explicit.
// Nuxt 3
export default defineNuxtConfig({
routeRules: {
'/blog/**': { isr: 3600 }
}
})
Next.js configures rendering in route files. Each page exports configuration. Configuration is distributed across files.
// Next.js
export const revalidate = 3600 // ISR with 1-hour cache
Deployment flexibility differs significantly. Nuxt 3 uses Nitro, which adapts to any deployment target. You can deploy the same codebase to Vercel, Netlify, Cloudflare Workers, AWS Lambda, Google Cloud, or a traditional Node.js server. Nitro generates the correct build for each platform.
Next.js works best on Vercel. Other platforms require additional configuration and may not support all features. Self-hosted Next.js needs more setup.
Ecosystem is the main differentiator. React has more libraries, more developers, and more community resources. Vue has excellent libraries and a strong community, but smaller than React.
When to Choose Each Framework
Choose Nuxt 3 if you:
- Prefer Vue over React
- Need deployment flexibility (multi-cloud, self-hosted)
- Want centralized routing configuration
- Like convention over configuration
- Need official modules for common features (SEO, PWA, i18n)
Choose Next.js if you:
- Prefer React over Vue
- Plan to deploy on Vercel
- Want the largest ecosystem
- Need React Server Components
- Prefer distributed route configuration
In 2025, the question isn't which framework is better. It's which rendering strategy you're using. Both Nuxt 3 and Next.js excel at hybrid rendering. Choose based on language preference and deployment target.
The technical capabilities are nearly identical. Your choice comes down to team skills and infrastructure preferences.
Migration Guide: Adding Hybrid Rendering to Existing Nuxt 3 Sites
You can add hybrid rendering to existing Nuxt 3 sites without major refactoring. The process is gradual and safe.
Step-by-Step Process
Step 1: Audit Current Routes
List all routes in your application. Categorize them by update frequency and content type.
Create a spreadsheet:
- Route pattern
- Current rendering mode
- Update frequency (hourly, daily, weekly, monthly, never)
- SEO importance (high, medium, low, none)
- Recommended rendering mode
Example:
| Route | Current | Update Freq | SEO | Recommended |
|---|---|---|---|---|
/ |
SSR | Weekly | High | SSG |
/about |
SSR | Monthly | Medium | SSG |
/blog/** |
SSR | Daily | High | ISR (1 hour) |
/products/:id |
SSR | Hourly | High | ISR (5 min) |
/account/** |
SSR | Real-time | None | SSR |
/admin/** |
SSR | Real-time | None | SPA |
Step 2: Start Conservative
Begin with your homepage. It's the easiest win.
// nuxt.config.ts - Week 1
export default defineNuxtConfig({
routeRules: {
'/': { prerender: true }
}
})
Deploy this change. Monitor for 1-2 days. Verify the homepage loads correctly. Check analytics for any issues.
Step 3: Expand Gradually
Add more static pages. These are low-risk changes.
// nuxt.config.ts - Week 2
export default defineNuxtConfig({
routeRules: {
'/': { prerender: true },
'/about': { prerender: true },
'/contact': { prerender: true },
'/privacy': { prerender: true },
'/terms': { prerender: true }
}
})
Deploy. Monitor. Verify.
Step 4: Add ISR for Dynamic Content
Start with longer TTL values. You can shorten them later.
// nuxt.config.ts - Week 3
export default defineNuxtConfig({
routeRules: {
'/': { prerender: true },
'/about': { prerender: true },
'/contact': { prerender: true },
'/privacy': { prerender: true },
'/terms': { prerender: true },
// Start with conservative 1-hour cache
'/blog/**': {
isr: 3600,
headers: {
'cache-control': 'public, max-age=3600, s-maxage=3600'
}
}
}
})
Monitor blog pages. Check if content freshness is acceptable. If 1 hour is too long, reduce to 30 minutes. If it's fine, you can increase to 2 hours.
Step 5: Optimize and Adjust
Monitor server load, cache hit rates, and user experience. Adjust TTL values based on real data.
// nuxt.config.ts - Week 4 (optimized)
export default defineNuxtConfig({
routeRules: {
'/': { prerender: true },
'/about': { prerender: true },
'/contact': { prerender: true },
'/privacy': { prerender: true },
'/terms': { prerender: true },
// Optimized based on monitoring
'/blog/**': { isr: 1800 }, // 30 minutes worked better
'/products/:id': { isr: 300 }, // 5 minutes for inventory
'/category/**': { swr: 120 }, // 2 minute SWR for fast browsing
// Keep dynamic routes as SSR
'/account/**': {
ssr: true,
headers: {
'cache-control': 'private, no-cache'
}
},
// Move admin to SPA
'/admin/**': {
ssr: false,
headers: {
'x-robots-tag': 'noindex'
}
}
}
})
Most sites can migrate to hybrid rendering in 1-2 weeks with zero downtime. The gradual approach reduces risk and lets you learn as you go.
Testing Your Hybrid Rendering Setup
Verify your configuration works correctly. Test both rendering behavior and SEO impact.
Verify Rendering Modes
Check which rendering mode served each page. Use curl to inspect headers and content.
# Test SSG page - should return instantly with static HTML
curl -I https://yoursite.com/
# Look for:
# - cache-control header with long max-age
# - cf-cache-status: HIT (if using Cloudflare)
# - Fast response time
# Test ISR page - check cache headers
curl -I https://yoursite.com/blog/post-1
# Look for:
# - cache-control with max-age matching your ISR TTL
# - age header showing cache freshness
# Test SSR page - should have private or short cache
curl -I https://yoursite.com/account/dashboard
# Look for:
# - cache-control: private or no-cache
# - set-cookie headers (for authentication)
# View full HTML to verify server-rendering
curl https://yoursite.com/blog/post-1 | grep "<title>"
# Should show complete title tag, not placeholder
Headers tell you if caching works correctly. Fast responses indicate cache hits.
Test with Google Search Console
Verify Google crawls and indexes your pages correctly.
- Submit updated sitemap in Search Console
- Use URL Inspection tool on key pages
- Request indexing for important routes
- Check "Coverage" report for errors
- Monitor "Core Web Vitals" report for performance
Pages should show "Crawled - currently indexed" status. Core Web Vitals should be green for SSG and ISR pages.
Validate SEO Tags
Use these tools to verify meta tags and structured data:
- View Page Source - Right-click any page, view source, check tags are in initial HTML
- Google Rich Results Test - Test structured data rendering
- Meta Tags Checker - Verify Open Graph and Twitter cards
- Schema Markup Validator - Validate JSON-LD structured data
Server-rendered pages should show all tags in the initial HTML. SPA pages will have incomplete tags in the source.
Performance Testing
Test loading speed across rendering modes.
- Google PageSpeed Insights - Test Core Web Vitals
- WebPageTest - Detailed waterfall analysis
- Lighthouse - Comprehensive performance audit
- Chrome DevTools - Network timing
SSG pages should score 95-100 on Lighthouse. ISR pages should score 90-100. SSR pages should score 80-95. Large performance differences indicate configuration problems.
FAQ Section
What's the difference between ISR and SWR in Nuxt 3?
ISR (Incremental Static Regeneration) and SWR (Stale-While-Revalidate) both cache pages with TTL, but they handle expiration differently.
ISR blocks the first visitor after cache expiration. When cache expires, the next request waits for regeneration. That visitor experiences a slower response. Subsequent visitors get the fresh cached version instantly.
SWR serves cached content immediately, even after expiration. It returns the stale cached version to the visitor. In the background, it regenerates the page. The next visitor gets the updated version. No visitor ever waits for regeneration.
Use ISR when accuracy matters more than speed. Use SWR when user experience matters more than perfect freshness. For most cases, SWR provides better UX.
Can I use different rendering modes on the same page based on user state?
No, rendering mode is determined per route, not per user. The route /products/123 uses one rendering mode for all visitors.
You can personalize content within a rendering mode. Use SSR with server-side logic for different users. Use ISR or SSG with client-side personalization after page loads.
// SSR approach - different HTML per user
<script setup>
const user = await getCurrentUser()
</script>
<template>
<div v-if="user">
Welcome back, {{ user.name }}!
</div>
<div v-else>
Please log in
</div>
</template>
// ISR/SSG approach - client-side personalization
<script setup>
const user = ref(null)
onMounted(async () => {
user.value = await getCurrentUser()
})
</script>
<template>
<div v-if="user">
Welcome back, {{ user.name }}!
</div>
</template>
The SSR approach shows personalized content immediately. The ISR/SSG approach shows generic content, then personalizes after JavaScript loads.
How does hybrid rendering affect my hosting costs?
Hybrid rendering typically reduces hosting costs by 40-70% compared to pure SSR.
SSG pages cost almost nothing. They serve from CDN. No server processing required. CDN bandwidth is cheap.
ISR pages cost more than SSG but less than SSR. The first generation requires server time. Subsequent cached hits are free. A page with 1000 views and 10-minute TTL regenerates 144 times per day. You pay for 144 server requests instead of 1000.
SSR pages cost the most. Every view requires server processing. A page with 1000 views needs 1000 server executions.
Moving 10 marketing pages from SSR to SSG can reduce server costs by 60%. Those pages no longer hit your server at all.
Will Google index my ISR pages correctly?
Yes. Google crawls and indexes ISR pages the same as static pages. The crawler receives complete HTML. It sees all content, meta tags, and structured data.
From Google's perspective, ISR pages look identical to static pages. The HTML is fully rendered. No JavaScript execution is required. Crawling is efficient.
The only difference is cache freshness. ISR pages may show slightly different content when recrawled after cache expiration. This is fine. Google handles content updates naturally.
Can I use hybrid rendering with Nuxt Content?
Yes. Nuxt Content works perfectly with hybrid rendering. Content pages typically use SSG or ISR.
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxt/content'],
routeRules: {
// Static for documentation
'/docs/**': { prerender: true },
// ISR for blog with updates
'/blog/**': {
isr: 3600,
headers: {
'cache-control': 'public, max-age=3600'
}
}
}
})
Nuxt Content generates routes from markdown files. The prerenderer discovers these routes automatically. You don't need to list them manually.
Use SSG if your content rarely changes. Use ISR if you update content regularly and don't want to rebuild the entire site.
How do I purge ISR cache when content changes?
Cache purging depends on your deployment platform. Most platforms offer cache purging APIs.
Cloudflare Workers:
// server/api/purge-cache.post.ts
export default defineEventHandler(async (event) => {
const { url } = await readBody(event)
// Purge specific URL from Cloudflare cache
await $fetch(`https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/purge_cache`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${CF_API_TOKEN}`,
'Content-Type': 'application/json'
},
body: {
files: [url]
}
})
return { purged: true }
})
Vercel:
// server/api/purge-cache.post.ts
export default defineEventHandler(async (event) => {
const { url } = await readBody(event)
// Purge using Vercel API
await $fetch(`https://api.vercel.com/v1/purge`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${VERCEL_TOKEN}`
},
body: {
url
}
})
return { purged: true }
})
Cache tags provide another approach. Tag ISR pages, then purge by tag:
routeRules: {
'/products/:id': {
isr: 300,
headers: {
'cache-control': 'public, max-age=300',
'cache-tag': 'products'
}
}
}
// Then purge all products:
// Cloudflare: purge by tag "products"
// Vercel: purge by tag "products"
Most platforms support either URL-based or tag-based purging. Check your platform's documentation for specific implementation details.
Conclusion
Hybrid rendering is the future of Nuxt 3 SEO. The ability to choose rendering strategies per route gives you unprecedented optimization power. Marketing pages load instantly with SSG. Product catalogs stay fresh with ISR. User dashboards personalize with SSR. Admin panels run efficiently with SPA mode.
The four rendering modes each serve specific needs. Use SSG for static content that changes rarely. Use SSR for dynamic content that changes per user. Use ISR for content that updates regularly but tolerates brief staleness. Use SPA for authenticated areas that don't need SEO.
Route rules make implementation simple. Configure patterns in nuxt.config.ts. Nitro handles the complexity. Deploy to any platform. Your app adapts automatically.
Real-world examples show the power of hybrid rendering. E-commerce sites optimize product pages differently from marketing pages. Blogs use ISR for fresh content without constant rebuilds. SaaS apps combine static marketing with dynamic applications.
SEO optimization improves across the board. Server-rendered meta tags appear immediately for crawlers. Structured data integrates seamlessly. Sitemaps include the right pages. Core Web Vitals scores improve dramatically.
Start by converting your homepage to SSG today. The change takes five minutes. Deploy it. Monitor the results. You'll see faster loading and better SEO metrics within days.
Audit your routes for quick wins. Marketing pages, legal pages, and documentation are perfect SSG candidates. Move them over. Watch your server costs drop.
Measure with Google Search Console. Track Core Web Vitals improvements. Monitor crawl efficiency. The data will guide your next optimizations.
With Vercel's acquisition of NuxtLabs in July 2025, Nuxt 3's hybrid rendering capabilities will only improve. The framework is positioned to dominate Vue-based SSR for the next 3-5 years. Master hybrid rendering now, and you'll have a competitive advantage as the ecosystem matures.
The best rendering strategy isn't SSR or SSG. It's knowing when to use each. Nuxt 3 gives you that power. Use it wisely, and you'll outperform competitors still locked into monolithic rendering approaches.
Your users get faster pages. Search engines get better content. Your servers handle less load. Your costs go down. Your rankings go up.
That's the power of Nuxt 3 hybrid rendering.
