<v/>
{ }
Nuxt 3 · Nitro · Server Routes · Composables · SSR
Nuxt 3 Architektur verstehen
Server Routes, Composables und Nitro richtig einsetzen

Nuxt 3 ist mehr als ein Vue-3-Framework mit File-Based-Routing. Die Nitro Engine, Server Routes, Auto-Import und die klare Trennung von Server- und Client-Schichten sind ein durchdachtes Architekturmodell — das nur dann seine Stärken zeigt, wenn man versteht, warum es so gebaut ist.

22 Min. Lesezeit Nitro · Server Routes · useFetch · $fetch · Auto-Import · Middleware Nuxt 3 · Vue 3 · TypeScript · H3

1. Die drei Schichten der Nuxt 3 Architektur

Wer Nuxt 3 nur als „Vue 3 mit File-Based-Routing" versteht, unterschätzt, was das Framework wirklich leistet. Das Framework-Design folgt einem klaren Schichtenmodell, bei dem jede Schicht eine eigene Laufzeitumgebung, eigene APIs und eigene Zuständigkeiten hat. Diese Trennung ist kein Zufall, sondern bewusste Architekturentscheidung — sie erlaubt Nuxt 3, dieselbe Codebasis auf einem klassischen Node.js-Server, auf serverless Functions und auf Edge-Runtimes wie Cloudflare Workers zu betreiben.

Die Nuxt 3 Architektur besteht aus drei klar getrennten Schichten, die unterschiedliche Verantwortlichkeiten tragen. Die erste Schicht ist die Nitro-Engine — der universelle Server, der Nuxt 3 von einem einfachen Vue-3-Framework unterscheidet. Die zweite Schicht ist die Vue-3-Client-Anwendung — Pages, Layouts, Komponenten und Composables, die im Browser rendern und per SSR vorgerendert werden. Die dritte Schicht ist der Build-Layer — Vite als Entwicklungsserver und Rollup als Produktions-Bundler, koordiniert durch Nuxt selbst.

Jede dieser drei Schichten hat eine strikt abgegrenzte Laufzeitumgebung: Nitro kennt keinen DOM, Vue-Komponenten dürfen nicht direkt auf Datenbanken zugreifen, und der Build-Layer ist zur Laufzeit komplett unsichtbar. Diese Grenzen zu kennen und zu respektieren ist die Grundvoraussetzung für eine wartbare Nuxt 3 Architektur.

Das Verständnis dieser drei Schichten ist entscheidend, um die richtigen Nuxt-3-Werkzeuge für jede Aufgabe zu wählen. Datenbankzugriffe, API-Authentifizierung und sensible Business-Logik gehören in die Nitro-Schicht — Server Routes und Server Middleware. Interaktive UI-Komponenten, reaktive Zustände und Nutzerinteraktionen gehören in die Vue-Schicht. Build-Konfigurationen, Module und Layer-Extensions gehören in den Build-Layer. Wer Code in der falschen Schicht platziert — etwa Datenbankzugriffe direkt in einer Vue-Komponente ohne Server Route — produziert Sicherheitslücken oder Code, der im SSR-Kontext fehlschlägt.

2. Nitro: der universelle Server hinter Nuxt 3

Nitro ist das Herzstück der Nuxt 3 Architektur und gleichzeitig das am wenigsten verstandene Element. Nitro ist ein eigenständiges Framework für serverseitige Anwendungen, das mit dem H3-Mikro-Framework aufgebaut ist. Es kompiliert den Server-Code zu einem einzigen, deploymentfähigen Bundle, das auf verschiedenen Plattformen läuft: Node.js, Edge Runtimes wie Cloudflare Workers, Vercel Edge, Netlify Functions, und mehr. Dieses universelle Deployment-Modell ist der Grund, warum eine Nuxt-3-Anwendung ohne Konfigurationsänderung von einem klassischen VPS zu einem CDN-Edge-Netzwerk verschoben werden kann.

Nitro bietet eingebautes Caching mit konfigurierbaren Cache-Strategien, ein Event-System basierend auf H3, Server-seitiges Rendering und die Ausführung von Server Routes. Das Nitro-Cache-System ist besonders mächtig: Server Routes können ihre Responses mit einer Zeile Konfiguration cachen — nach Zeit, nach Request-Parameter oder nach Custom-Keys. Dieses Caching findet auf dem Server statt, bevor der Request die Vue-Anwendung überhaupt erreicht, und reduziert die Server-Last bei häufig angeforderten Daten drastisch. Der folgende Abschnitt zeigt, wie Server Routes in der Praxis aufgebaut werden.

3. Server Routes: leichtgewichtiger API-Layer in /server/api

Nuxt 3 Server Routes sind Handler-Funktionen im Verzeichnis /server/api/ und /server/routes/, die automatisch zu API-Endpunkten werden. Sie laufen ausschließlich auf dem Server, haben Zugriff auf die Nuxt-Server-Konfiguration und können direkt auf Datenbanken, externe APIs und Environment-Variablen zugreifen — ohne dass der Client-Code jemals den Zugriffs-Token sieht. Das ist der fundamentale Sicherheitsvorteil gegenüber direkten API-Aufrufen aus dem Client.

Die Datei-zu-Route-Konvention der Nuxt 3 Server Routes ist intuitiv: /server/api/products.get.ts wird zu GET /api/products, /server/api/products.post.ts zu POST /api/products, und /server/api/products/[id].ts zu /api/products/:id für alle HTTP-Methoden. Das Suffix im Dateinamen (.get, .post, .put, .delete) beschränkt den Handler auf eine HTTP-Methode. Handler ohne Suffix akzeptieren alle Methoden und können intern mit getMethod(event) differenzieren.


// server/api/products/[id].get.ts
// Nuxt 3 Server Route — runs on server only, never in the browser bundle
import { z } from 'zod'

// Define route-level cache: response cached for 5 minutes by product ID
export default defineCachedEventHandler(async (event) => {
  const id = getRouterParam(event, 'id')

  // Input validation with zod — rejects invalid IDs before DB query
  const schema = z.string().uuid()
  const parsed = schema.safeParse(id)
  if (!parsed.success) {
    throw createError({ statusCode: 400, message: 'Invalid product ID format' })
  }

  // DB access — safe here because this code never runs in the browser
  const product = await db.products.findUnique({ where: { id: parsed.data } })
  if (!product) {
    throw createError({ statusCode: 404, message: 'Product not found' })
  }

  return product
}, {
  maxAge: 60 * 5,          // cache for 5 minutes
  getKey: (event) => `product:${getRouterParam(event, 'id')}`,
})

4. Composables und Auto-Import: wie Nuxt 3 das löst

Das Auto-Import-System von Nuxt 3 ist eines der kontroversesten Features — und eines der durchdachtesten, wenn man versteht, wie es funktioniert. Dateien in /composables/, /utils/ und /components/ werden automatisch in jeden Component-Scope importiert, ohne dass explizite Import-Statements nötig sind. Nuxt analysiert den Code statisch zur Build-Zeit und fügt die fehlenden Imports automatisch ein. Das bedeutet: Kein IDE-Confusion, kein versteckter globaler State — nur compile-time-generierte Imports.

Eigene Composables in /composables/ werden ebenfalls auto-importiert und können die eingebauten Nuxt-3-Composables wie useFetch, useState und useRouter erweitern oder wrappen. Der Unterschied zu Vue-Composables außerhalb von Nuxt: Nuxt-Composables haben Zugriff auf den SSR-Kontext und können Server-seitige Daten mit Client-seitigem Hydrations-State verbinden. Ein useProducts-Composable, das intern useFetch verwendet, funktioniert sowohl beim initialen Server-Render als auch bei nachfolgenden Client-seitigen Navigationen — ohne Code-Duplikation oder Sonderfälle.


// composables/useProducts.ts
// Auto-imported by Nuxt — works on server (SSR) and client (navigation)
export function useProducts(categoryId?: Ref<string>) {
  // useFetch is SSR-aware: fetches on server for initial render,
  // then uses cached data for client-side hydration
  const { data: products, status, error, refresh } = useFetch(
    () => `/api/products${categoryId?.value ? `?category=${categoryId.value}` : ''}`,
    {
      key: () => `products-${categoryId?.value ?? 'all'}`,  // deduplicate requests with same key
      watch: [categoryId],   // auto-refetch when categoryId changes
      transform: (data) => data.map(normalizeProduct),  // transform before caching
    }
  )

  const isLoading = computed(() => status.value === 'pending')
  const isEmpty   = computed(() => !isLoading.value && products.value?.length === 0)

  return { products, isLoading, isEmpty, error, refresh }
}

5. useFetch vs. $fetch: wann welches Werkzeug?

Die häufigste Verwirrung in der Nuxt 3 Architektur: Wann useFetch, wann useAsyncData, wann $fetch? Die Antwort hängt vom Kontext ab. useFetch ist die Komposition aus useAsyncData und $fetch — es kombiniert Datenabruf, Caching, SSR-Hydration und reaktive URL-Verknüpfung in einem Aufruf. Es ist die richtige Wahl für Daten, die beim initialen Page-Render benötigt werden und Teil des SSR-Outputs sein sollen.

$fetch ist das rohe ofetch-basierte HTTP-Client-Objekt ohne SSR-Bewusstsein. Es ist die richtige Wahl für programmatische API-Calls, die nicht beim Page-Render ausgelöst werden — Button-Clicks, Form-Submissions, Mutations. useFetch in einer Button-Click-Handler zu verwenden ist ein häufiger Fehler: Es erstellt eine reaktive Binding, die beim Komponenten-Unmount nicht automatisch abgebrochen wird, und teilt den Request-State global mit allen Instanzen desselben key. Für imperative API-Calls ist $fetch das semantisch korrekte Werkzeug.

6. Middleware in Nuxt 3: Route Guards und Server Middleware

Nuxt 3 hat zwei verschiedene Middleware-Konzepte, die oft verwechselt werden. Route-Middleware läuft im Client-Router — JavaScript-Funktionen in /middleware/, die vor jeder Navigation ausgeführt werden. Sie dienen als Route Guards: Authentifizierung prüfen, Redirects ausführen, Analytics-Events senden. Server-Middleware läuft in der Nitro-Engine — Funktionen in /server/middleware/, die jeden HTTP-Request abfangen, bevor er einen Handler erreicht. Sie dienen für CORS-Headers, Rate-Limiting, Request-Logging und JWT-Validierung.

Der entscheidende Unterschied: Route-Middleware hat Zugriff auf Nuxt-State und Vue-Router, läuft aber nur im Browser (außer bei serverseitig gerendertem initialen Request). Server-Middleware hat Zugriff auf den vollständigen HTTP-Request und alle Server-Ressourcen, läuft aber komplett unabhängig vom Vue-Client-Code. Authentifizierungs-Logic, die auf Cookies oder JWT-Tokens basiert, gehört in die Server-Middleware — dort kann sie nicht vom Client umgangen werden. Route-Middleware ist die zweite Sicherheitsschicht für UX-Feedback, nicht die einzige.

7. Plugins und App-Lifecycle in Nuxt 3

Nuxt-3-Plugins in /plugins/ werden automatisch beim App-Start ausgeführt und können den nuxtApp-Kontext erweitern. Sie sind das richtige Werkzeug, um globale Bibliotheken zu initialisieren, Direktiven zu registrieren und globale State-Provider einzurichten. Das Suffix .server.ts beschränkt ein Plugin auf den Server-Render, .client.ts auf den Browser. Ohne Suffix läuft das Plugin auf beiden Seiten.

Ein wichtiges Detail der Nuxt 3 Architektur: Plugins können async sein. Ein async Plugin pausiert den Nuxt-Startup-Prozess, bis das Plugin aufgelöst hat. Das ist sinnvoll für Plugins, die Konfiguration laden oder externe Services initialisieren müssen. Aber es verzögert den First-Render — async Plugins sollten so kurz wie möglich gehalten werden und keine Netzwerkrequests enthalten, die auch later geladen werden könnten.

8. Server- vs. Client-only Code: .server.ts und .client.ts

Das Suffix-System von Nuxt 3 für Server- und Client-only-Dateien ist ein elegantes Architekturmuster. Eine Datei analytics.client.ts wird nur in das Client-Bundle eingebunden — sie schlägt auf dem Server nicht fehl, weil sie dort einfach nicht existiert. Eine Datei dbClient.server.ts erscheint nie im Client-Bundle — der Datenbankcode und alle seine Imports sind physisch ausgeschlossen. Das verhindert Bundle-Aufblähung durch Server-only-Bibliotheken und verhindert, dass sensible Server-Code-Teile im Browser inspizierbar sind.

Für Komponenten funktioniert dasselbe Muster: Eine HeavyChart.client.vue-Komponente wird nur im Browser gerendert, nie auf dem Server. Der Server liefert einen Placeholder, der Browser ersetzt ihn nach der Hydration. Das ist die empfohlene Lösung für Komponenten, die Browser-APIs (window, document, Canvas) benötigen — statt des häufigen Fehlers, window-Zugriffe manuell mit if (process.client) zu wrappen.

9. Nuxt 3 Rendering-Modi im Vergleich

Nuxt 3 unterstützt mehrere Rendering-Modi, die pro Route konfiguriert werden können — eine Funktion, die die Nuxt 3 Architektur flexibel genug für hybride Anwendungen macht. Die folgende Tabelle zeigt die Unterschiede und wann welcher Modus sinnvoll ist.

Modus Nuxt-Konfiguration Einsatzgebiet Caching
SSR (Standard) ssr: true Dynamische Seiten, SEO, Auth Nitro-Cache pro Response
SSG / Pre-rendering nitro.prerender Blogs, Docs, statische Inhalte CDN-Cache (statische Dateien)
Client-only (SPA) ssr: false Admin-UIs, Dashboard, kein SEO nötig Browser-Cache, kein Server
Hybrid Rendering routeRules per Route Marketing-Seiten + App gemischt Pro Route konfigurierbar
ISR (Incremental) routeRules.isr: 60 Produkt-/Blog-Seiten mit Update-Bedarf Stale-While-Revalidate

Das Hybrid-Rendering-Modell ist eine der stärksten Funktionen der Nuxt 3 Architektur. Mit routeRules in der nuxt.config.ts lassen sich verschiedene Caching- und Rendering-Strategien pro URL-Pattern konfigurieren: Marketing-Seiten werden statisch vorgerendert, Produktseiten mit ISR aktualisiert, und die Checkout-Route läuft vollständig SSR ohne Cache. Diese Granularität erlaubt es, Performance-Optimierungen exakt dort anzusetzen, wo sie den größten Effekt haben.

Mironsoft

Nuxt 3 Architektur, Server Routes und Full-Stack-Vue-Entwicklung

Nuxt 3 Projekte, die architektonisch solide aufgebaut sind?

Wir bauen Nuxt-3-Anwendungen mit klarer Schichtentrennung, Nitro-Caching, typisieren Server Routes und eingebetteten Composables — skalierbar für Teams und Deployment-Anforderungen.

Server Routes Design

Typisierte API-Layer mit Nitro-Caching, Validierung und Auth-Middleware

Composable-Architektur

useFetch-Wrapper, Auto-Import-Strategie und SSR-Hydration sauber umgesetzt

Hybrid Rendering

routeRules für SSR, SSG, ISR und Client-only je nach Seitentyp konfiguriert

10. Zusammenfassung

Die Nuxt 3 Architektur ist ein durchdachtes Schichtenmodell: Nitro als universeller Server mit eingebautem Caching und Edge-Deployment-Fähigkeit, Server Routes als sicherer API-Layer ohne separates Backend-Repository, Composables mit Auto-Import als klare Abstraktionsschicht für reaktive Logik, und ein flexibles Rendering-Modell von SSR über SSG bis zu Hybrid-Routing. Das Verständnis dieser Schichten ist die Voraussetzung, um Nuxt-3-Entscheidungen zu treffen, die mit wachsendem Projekt nicht zur Last werden.

Die häufigen Fehler in Nuxt-3-Projekten entstehen durch Schichtenverwirrung: Datenbankcode in Vue-Komponenten, useFetch in Event-Handlern, Authentifizierung nur in Route-Middleware statt auch in Server-Middleware, und .server.ts-Dateien, die Browser-APIs importieren. Mit einem klaren Bild der drei Schichten und ihrer Zuständigkeiten lassen sich diese Fehler systematisch vermeiden — von der ersten Zeile Code an.

Der langfristige Architektur-Vorteil von Nuxt 3 zeigt sich besonders dann, wenn Projekte wachsen: Neue Server Routes lassen sich hinzufügen, ohne bestehende Client-Komponenten anzufassen. Neue Composables werden automatisch importiert, ohne Build-Konfigurationen zu ändern. Das Rendering-Modell kann pro Route angepasst werden, wenn sich Anforderungen ändern — ohne Refactoring der gesamten Anwendung. Diese Flexibilität ist das Ergebnis der klaren Schichtentrennung, die Nuxt 3 von Beginn an als Designprinzip verfolgt.

Nuxt 3 Architektur — Das Wichtigste auf einen Blick

Nitro Engine

Universeller Server hinter Nuxt 3. Eingebautes Caching, Edge-Deployment, H3-basiert. Kompiliert zu einem deploymentfähigen Bundle für alle Plattformen.

Server Routes

/server/api/ als sicherer API-Layer. Datenbankzugriffe, Auth und sensible Logik hier — nie im Client-Code. Nitro-Caching mit defineCachedEventHandler.

useFetch vs. $fetch

useFetch: SSR-aware, reaktiv, für Page-Render-Daten. $fetch: programmatisch, imperativ, für Button-Clicks und Mutations. Nicht austauschbar.

Hybrid Rendering

routeRules in nuxt.config.ts: SSR, SSG, ISR und SPA pro Route. Marketing-Seiten statisch, Checkout SSR, Admin SPA — alles in einem Nuxt-Projekt.

11. FAQ: Nuxt 3 Architektur, Server Routes und Nitro

1Nuxt 3 vs. reines Vue 3 — was ist der Unterschied?
Vue 3: reaktives UI-Framework. Nuxt 3: Full-Stack darüber — File-Based-Routing, SSR, Nitro-Server, Auto-Import, Server Routes und Edge-Deployment ohne Konfiguration.
2Was ist Nitro?
Universeller Server hinter Nuxt 3. Kompiliert zu einem Bundle für Node.js, Edge Runtimes und Serverless. Eingebautes Caching, H3-Request-Handling.
3useFetch oder $fetch?
useFetch: Page-Render-Daten, SSR-aware, reaktiv, gecacht. $fetch: programmatisch, Event-Handler, Button-Clicks, Form-Submit. Nicht austauschbar.
4Server Routes absichern?
JWT/Session-Validierung in /server/middleware/. Eingaben mit zod validieren. Authorization-Checks vor Datenzugriff in der Route selbst.
5Route vs. Server Middleware?
Route Middleware: Vue-Router, Client + initial SSR, für Guards und Analytics. Server Middleware: Nitro, jeden HTTP-Request, für Auth, CORS, Rate-Limiting.
6.server.ts und .client.ts?
.server.ts: nur Server-Bundle, nie im Browser. .client.ts: nur Client-Bundle, existiert auf dem Server nicht. Ideal für Browser-API-abhängigen Code.
7Nitro-Caching für Server Routes?
defineCachedEventHandler mit maxAge und getKey-Funktion. Response wird gecacht — folgende Requests ohne DB-Zugriff direkt beantwortet.
8Hybrides Rendering konfigurieren?
routeRules in nuxt.config.ts: prerender für Marketing, ssr: false für Admin, isr: 60 für Produktseiten. Pro Route-Pattern konfigurierbar.
9Warum keine DB-Aufrufe in Komponenten?
DB-Code landet im Client-Bundle — Credentials sichtbar. Node.js-Libraries schlagen im Browser fehl. Server Routes: Server-only, sicher, cachebar.
10Vorteil von Auto-Import?
Composables und Komponenten ohne Import-Statements verfügbar. Nuxt generiert sie statisch zur Build-Zeit — kein globaler State, volle IDE-Unterstützung.