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.

Illustration zu serverseitigem Rendering und statischer Generierung mit Nuxt 3 und Vue.js

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.

Voraussetzungen

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.

nuxt.config.tstypescript
// 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.

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

vue
<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/plugins/render-hooks.tstypescript
// 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'
    }
  })
})
SSR-Performance

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.

nuxt.config.tstypescript
// 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.

server/utils/generate-routes.tstypescript
// 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.

nuxt.config.tstypescript
// 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.

server/api/articles/[slug].get.tstypescript
// 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.

server/api/articles/[slug].put.tstypescript
// 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
})
Verteilter Cache

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.

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

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

nuxt.config.tstypescript
// 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.

bash
# 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-Server

Fang 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

#nuxt 3
#vue.js
#ssr
#statische generierung
#web-performance

Teilen

Verwandte Artikel