Nuxt 3: SSR und statische Generierung, der vollständige Leitfaden
SSR und statische Generierung mit Nuxt 3 meistern. Von useFetch bis Route Rules: So lässt sich die Performance von Vue.js-Anwendungen optimieren.

Nuxt 3 verändert die Art und Weise, wie Vue.js-Anwendungen gebaut werden, indem es mehrere Rendering-Modi für unterschiedliche Anwendungsfälle bereitstellt. Vom Server-Side Rendering (SSR) über die statische Generierung bis hin zum hybriden Rendering bietet das Framework bemerkenswerte Flexibilität, um Performance und SEO zu optimieren.
Dieses Tutorial setzt grundlegende Kenntnisse von Vue 3 und der Composition API voraus. Vertrautheit mit Konzepten des serverseitigen Renderings ist hilfreich, aber nicht zwingend erforderlich, da die Grundlagen im Verlauf des Leitfadens erklärt werden.
Die Rendering-Modi von Nuxt 3 verstehen
Bevor man in den Code eintaucht, ist es wichtig, die Unterschiede zwischen den verfügbaren Rendering-Modi zu verstehen. Jeder Modus deckt spezifische Anforderungen in Bezug auf Performance, SEO und Benutzererfahrung ab.
SSR (Server-Side Rendering) generiert das HTML serverseitig bei jeder Anfrage. Die statische Generierung (SSG) erzeugt alle Seiten zur Build-Zeit. Der Hybridmodus erlaubt es, beide Ansätze pro Seite zu kombinieren.
// Konfiguration der verschiedenen Rendering-Modi
export default defineNuxtConfig({
// SSR standardmäßig aktiviert (für SEO empfohlen)
ssr: true,
// Statische Generierung: rendert alle Seiten vorab
// 'npm run generate' verwenden, um zu bauen
// target: 'static', // Nuxt-2-Syntax
// Hybridmodus: pro Route konfigurierbar
routeRules: {
// Startseite: vorgerendert und im Cache
'/': { prerender: true },
// Blog: statische Generierung
'/blog/**': { prerender: true },
// Dashboard: Rendering ausschließlich im Client
'/dashboard/**': { ssr: false },
// API: kein Pre-Rendering
'/api/**': { prerender: false }
}
})Diese Konfiguration zeigt die Stärke des Hybridmodus: Jeder Bereich der Anwendung verwendet den Rendering-Modus, der seinen Anforderungen am besten entspricht.
Datenabfrage mit useFetch und useAsyncData
Nuxt 3 stellt zwei Hauptkomposables für isomorphes Datenladen bereit. Diese Komposables funktionieren sowohl serverseitig als auch clientseitig und übernehmen die Hydration automatisch.
useFetch ist ein Wrapper um useAsyncData, der HTTP-Aufrufe vereinfacht. useAsyncData bietet mehr Kontrolle für fortgeschrittene Anwendungsfälle.
<script setup lang="ts">
// pages/blog/[slug].vue
// Detailseite eines Artikels mit useFetch
// Routenparameter abrufen
const route = useRoute()
// useFetch: automatisches Datenladen
// Daten werden serverseitig geladen und im Client hydriert
const { data: article, pending, error } = await useFetch(
`/api/articles/${route.params.slug}`,
{
// Eindeutiger Schlüssel für Cache und Deduplizierung
key: `article-${route.params.slug}`,
// Daten bei Bedarf transformieren
transform: (response) => response.data,
// Cache-Optionen
getCachedData: (key) => {
// Prüfen, ob Daten im Cache liegen
const nuxtApp = useNuxtApp()
return nuxtApp.payload.data[key]
}
}
)
// Fehlerbehandlung mit Navigation
if (error.value) {
throw createError({
statusCode: 404,
message: 'Artikel nicht gefunden'
})
}
</script>
<template>
<div>
<div v-if="pending" class="loading">
Artikel wird geladen...
</div>
<article v-else-if="article">
<h1>{{ article.title }}</h1>
<div v-html="article.content" />
</article>
</div>
</template>Für Fälle, die mehr Kontrolle erfordern, erlaubt useAsyncData die Ausführung beliebiger asynchroner Funktionen.
<script setup lang="ts">
// pages/products/index.vue
// Produktliste mit useAsyncData und Filtern
const route = useRoute()
// useAsyncData: volle Kontrolle über die Fetching-Logik
const { data: products, refresh } = await useAsyncData(
'products-list',
async () => {
// Daten bei Bedarf aus mehreren Quellen abrufen
const [productsResponse, categoriesResponse] = await Promise.all([
$fetch('/api/products', {
query: {
category: route.query.category,
sort: route.query.sort || 'date'
}
}),
$fetch('/api/categories')
])
// Daten kombinieren und transformieren
return {
products: productsResponse.data,
categories: categoriesResponse.data,
total: productsResponse.meta.total
}
},
{
// Bei Änderung der Query-Parameter neu laden
watch: [() => route.query]
}
)
// Funktion zum manuellen Aktualisieren
const updateFilters = async (newCategory: string) => {
await navigateTo({
query: { ...route.query, category: newCategory }
})
}
</script>Diese Komposables vermeiden Doppel-Fetches: Auf dem Server geladene Daten werden in das HTML-Payload serialisiert und während der clientseitigen Hydration wiederverwendet.
SSR mit Server-Hooks anpassen
Das SSR von Nuxt 3 lässt sich über Server-Hooks anpassen. Diese Hooks erlauben Eingriffe in verschiedenen Phasen des Rendering-Zyklus, um das Standardverhalten zu modifizieren.
// Server-Plugin zur Anpassung des SSR-Renderings
export default defineNitroPlugin((nitroApp) => {
// Hook, der vor dem Rendern jeder Seite ausgeführt wird
nitroApp.hooks.hook('render:html', (html, { event }) => {
// Skripte oder Metadaten injizieren
html.head.push(`
<script>
// Analytics oder globale Konfiguration
window.__APP_CONFIG__ = {
environment: '${process.env.NODE_ENV}',
apiUrl: '${process.env.API_URL}'
}
</script>
`)
})
// Hook für die Verwaltung des Render-Caches
nitroApp.hooks.hook('render:response', (response, { event }) => {
// Eigene Cache-Header hinzufügen
const path = event.path
if (path.startsWith('/blog/')) {
// Langer Cache für Blogartikel
response.headers['Cache-Control'] = 'public, max-age=3600, s-maxage=86400'
} else if (path.startsWith('/api/')) {
// Kein Cache für APIs
response.headers['Cache-Control'] = 'no-store'
}
})
})Der Hook render:response eignet sich ideal, um HTTP-Cache-Strategien umzusetzen. Die Kombination aus SSR und einem CDN, das Cache-Control-Header respektiert, ermöglicht es, vorgerenderte Seiten auszuliefern und sie bei Bedarf gezielt zu invalidieren.
Statische Generierung mit nuxt generate
Die statische Generierung baut alle Seiten zur Build-Zeit vorab. Dieser Ansatz eignet sich ideal für Websites mit stabilem Inhalt wie Blogs, Dokumentationen oder Marketing-Seiten.
Für dynamische Routen muss Nuxt alle zu generierenden URLs kennen. Der Hook prerender:routes erlaubt es, diese Routen programmatisch zu definieren.
// Vollständige Konfiguration für statische Generierung
export default defineNuxtConfig({
// Statische Generierung aktivieren
nitro: {
prerender: {
// Automatisches Crawlen von Links aktivieren
crawlLinks: true,
// Routen, die immer enthalten sein müssen
routes: ['/', '/about', '/contact'],
// Bestimmte Routen ignorieren
ignore: ['/admin', '/api']
}
},
hooks: {
// Hook zum Erzeugen dynamischer Routen
async 'prerender:routes'(ctx) {
// Artikel aus API oder DB laden
const articles = await fetch('https://api.example.com/articles')
.then(res => res.json())
// Artikel-Routen hinzufügen
for (const article of articles) {
ctx.routes.add(`/blog/${article.slug}`)
}
// Kategorien laden
const categories = await fetch('https://api.example.com/categories')
.then(res => res.json())
for (const category of categories) {
ctx.routes.add(`/category/${category.slug}`)
}
}
}
})Für Projekte mit vielen Seiten kann der automatische Crawler unzureichend sein. Hier ist ein robusterer Ansatz mit einer separaten Konfigurationsdatei.
// Hilfsfunktion zur Generierung der Liste dynamischer Routen
import { prisma } from './prisma'
export async function getAllStaticRoutes(): Promise<string[]> {
const routes: string[] = []
// Blogartikel
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}`)
}
// Produktseiten
const products = await prisma.product.findMany({
where: { active: true },
select: { slug: true }
})
for (const product of products) {
routes.push(`/products/${product.slug}`)
}
// Tag-Seiten
const tags = await prisma.tag.findMany({
select: { slug: true }
})
for (const tag of tags) {
routes.push(`/tags/${tag.slug}`)
}
return routes
}Bereit für deine Vue.js / Nuxt.js-Interviews?
Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.
Hybrides Rendering mit routeRules
Das hybride Rendering ist das Aushängeschild von Nuxt 3. Es erlaubt, pro Route unterschiedliche Rendering-Regeln zu definieren und so das Beste aus SSR und SSG zu kombinieren.
// Erweiterte Konfiguration für hybrides Rendering
export default defineNuxtConfig({
routeRules: {
// Marketing-Seiten: vorgerendert und langfristig im Cache
'/': { prerender: true },
'/pricing': { prerender: true },
'/features/**': { prerender: true },
// Blog: ISR (Incremental Static Regeneration)
// Revalidierung jede Stunde
'/blog/**': {
isr: 3600,
prerender: true
},
// Dokumentation: CDN-Cache mit Revalidierung
'/docs/**': {
swr: 86400, // Stale-while-revalidate
prerender: true
},
// E-Commerce: SSR mit kurzem Cache
'/products/**': {
ssr: true,
cache: {
maxAge: 60,
staleMaxAge: 300
}
},
// Warenkorb und Checkout: ausschließlich Client
'/cart': { ssr: false },
'/checkout/**': { ssr: false },
// Dashboard: SPA-Modus
'/dashboard/**': {
ssr: false,
// Pre-Rendering deaktivieren
prerender: false
},
// API-Routen: standardmäßig kein Cache
'/api/**': {
cors: true,
headers: {
'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE'
}
}
}
})Diese Konfiguration veranschaulicht eine typische Architektur moderner Anwendungen: Öffentliche Seiten werden mit SSG für SEO optimiert, während interaktive Bereiche auf clientseitiges Rendering setzen.
Performance-Optimierung mit Daten-Caching
Über das Seiten-Caching hinaus erlaubt Nuxt 3, geladene Daten zu cachen. Diese Strategie reduziert die Last auf APIs und verbessert die Antwortzeiten.
// API-Endpunkt mit Daten-Caching
import { getArticleBySlug } from '~/server/utils/articles'
export default defineCachedEventHandler(
async (event) => {
const slug = getRouterParam(event, 'slug')
if (!slug) {
throw createError({
statusCode: 400,
message: 'Slug fehlt'
})
}
const article = await getArticleBySlug(slug)
if (!article) {
throw createError({
statusCode: 404,
message: 'Artikel nicht gefunden'
})
}
return article
},
{
// Cache-Schlüssel basierend auf dem Slug
getKey: (event) => `article-${getRouterParam(event, 'slug')}`,
// Cache-Dauer: 1 Stunde
maxAge: 3600,
// Stale-while-revalidate: alten Cache während der Aktualisierung ausliefern
staleMaxAge: 7200,
// Tag-basierte Invalidierung
tags: ['articles']
}
)Um den Cache bei Inhaltsänderungen zu invalidieren, stellt Nuxt ein Tag-System bereit.
// Artikel-Update mit Cache-Invalidierung
import { updateArticle } from '~/server/utils/articles'
export default defineEventHandler(async (event) => {
const slug = getRouterParam(event, 'slug')
const body = await readBody(event)
// Artikel aktualisieren
const article = await updateArticle(slug, body)
// Cache für diesen Artikel invalidieren
await useStorage('cache').removeItem(`nitro:handlers:article-${slug}`)
// Oder Tag-basierte Invalidierung (alle Artikel)
// await useStorage('cache').clear('articles')
return article
})In der Produktion mit mehreren Instanzen reicht ein In-Memory-Cache nicht aus. Es empfiehlt sich, Redis oder ein anderes verteiltes System über die Nitro-Konfiguration einzurichten, um Konsistenz zwischen den Instanzen sicherzustellen.
SEO und Metadaten verwalten
SSR ermöglicht SEO-Optimierung, indem Metadaten serverseitig generiert werden. Nuxt 3 bietet mehrere Ansätze, um Meta-Tags dynamisch zu verwalten.
<script setup lang="ts">
// pages/blog/[slug].vue
// Blogseite mit optimiertem SEO
const route = useRoute()
const { data: article } = await useFetch(`/api/articles/${route.params.slug}`)
// Dynamische SEO-Konfiguration auf Basis des Artikels
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
})
// Strukturierte Daten für Google
useHead({
script: [
{
type: 'application/ld+json',
innerHTML: JSON.stringify({
'@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': 'Organization',
name: 'SharpSkill'
}
})
}
]
})
</script>Für statische Seiten lassen sich die Metadaten direkt in der Komponente definieren.
<script setup lang="ts">
// pages/about.vue
// Statische Seite mit SEO
definePageMeta({
title: 'Über uns'
})
useSeoMeta({
title: 'Über SharpSkill | Vorbereitung auf technische Interviews',
description: 'SharpSkill kennenlernen, die Plattform zur Vorbereitung auf technische Interviews. Mission: Entwicklerinnen und Entwicklern zum Erfolg in ihren technischen Interviews verhelfen.',
ogTitle: 'Über SharpSkill',
ogDescription: 'Die Plattform zur Vorbereitung auf technische Interviews',
ogImage: '/images/og-about.webp'
})
</script>Deployment und Produktionsüberlegungen
Die Wahl des Deployments hängt vom verwendeten Rendering-Modus ab. Hier die wichtigsten Optionen und ihre Konfigurationen.
// Konfiguration für verschiedene Deployment-Umgebungen
export default defineNuxtConfig({
nitro: {
// Preset je nach Zielplattform
// preset: 'vercel', // Vercel
// preset: 'netlify', // Netlify
// preset: 'cloudflare-pages', // Cloudflare
// preset: 'node-server', // Klassisches Node.js
// Konfiguration für Node.js in der Produktion
preset: 'node-server',
// Antwort-Komprimierung
compressPublicAssets: true,
// Konfiguration des Cache-Storages
storage: {
cache: {
driver: 'redis',
url: process.env.REDIS_URL
}
}
},
// Umgebungsvariablen zur Laufzeit
runtimeConfig: {
// Geheimnisse (nicht an den Client weitergegeben)
apiSecret: process.env.API_SECRET,
// Öffentliche Konfiguration
public: {
apiBase: process.env.NUXT_PUBLIC_API_BASE || '/api'
}
}
})Für ein statisches Deployment erzeugt der Befehl npm run generate einen Ordner .output/public, der bereit ist, auf jedem Static-File-Host bereitgestellt zu werden.
# Statische Generierung
npm run generate
# Der Inhalt von .output/public lässt sich bereitstellen auf:
# - Vercel (automatische Erkennung)
# - Netlify (automatische Konfiguration)
# - GitHub Pages
# - S3 + CloudFront
# - Jedem CDN oder Static-File-ServerFang an zu üben!
Teste dein Wissen mit unseren Interview-Simulatoren und technischen Tests.
Fazit
Nuxt 3 bietet außergewöhnliche Flexibilität beim Rendern von Vue.js-Anwendungen. Die Wahl zwischen SSR, SSG und hybridem Rendering hängt von den spezifischen Anforderungen jedes Projekts ab.
Kernpunkte:
✅ SSR: ideal für dynamische Inhalte, die gutes SEO erfordern (E-Commerce, Nachrichtenseiten)
✅ SSG: perfekt für stabile Inhalte (Blogs, Dokumentation, Marketing-Seiten)
✅ Hybrid: der beste Ansatz für komplexe Anwendungen mit unterschiedlichen Anforderungen
✅ useFetch/useAsyncData: automatische Hydration und Cache-Verwaltung
✅ routeRules: feingranulare Konfiguration für das Verhalten jeder Route
✅ Caching: vielfältige Strategien zur Optimierung der Performance in der Produktion
Die Kombination aus hybridem Rendering und einer durchdachten Cache-Strategie ermöglicht den Aufbau performanter Anwendungen, die SEO-optimiert sind und gleichzeitig die Interaktivität von Single Page Applications bewahren.
Tags
Teilen
Verwandte Artikel

Wichtige Vue.js-Interviewfragen: 25 Fragen für den Job
Vorbereitung auf Vue.js-Interviews mit diesen 25 wichtigen Fragen. Von Reaktivität bis zu Composables die Schlüsselkonzepte für das nächste Gespräch.

Vue 3 Pinia vs Vuex: Modernes State Management und Interviewfragen 2026
Pinia vs Vuex im direkten Vergleich: API-Design, TypeScript-Unterstützung, Performance, Migrationsstrategien und häufige Vue-Interviewfragen zum State Management 2026.

Vue 3 Composition API: Der vollständige Leitfaden zur Reaktivität
Die Vue 3 Composition API praxisnah erklärt: ref, reactive, computed, watch und Composables für leistungsstarke Vue-Anwendungen.