Modul 9: Nuxt Fortgeschritten

Error Handling

Behandle Fehler in Nuxt elegant – von globalen Fehlerseiten über Error Boundaries bis hin zu API-Fehlern.

Fehlerbehandlung in Nuxt

Jede Anwendung muss mit Fehlern umgehen – sei es ein 404 für eine nicht gefundene Seite, ein API-Fehler oder ein unerwarteter Laufzeitfehler. Nuxt bietet ein durchdachtes Error-Handling-System mit mehreren Ebenen:

  • error.vue – Globale Fehlerseite
  • NuxtErrorBoundary – Lokale Fehler in Komponenten abfangen
  • createError() – Fehler programmatisch auslösen
  • showError() / clearError() – Fehler anzeigen und zurücksetzen
🛤️

Rails-Vergleich

In Rails nutzt du rescue_from in Controllern und eigene Fehlerseiten in public/404.html oder app/views/errors/. Nuxt hat ein ähnliches Konzept: error.vue für die globale Fehlerseite und createError() statt raise ActiveRecord::RecordNotFound.

Die globale Fehlerseite: error.vue

Erstelle eine error.vue im Root-Verzeichnis deines Projekts (neben app.vue). Diese Seite wird automatisch angezeigt, wenn ein fataler Fehler auftritt:

error.vue
<!-- error.vue -->
<script setup lang="ts">
const props = defineProps<{
  error: {
    statusCode: number
    statusMessage?: string
    message?: string
  }
}>()

const handleError = () => clearError({ redirect: '/' })
</script>

<template>
  <div class="min-h-screen flex items-center justify-center">
    <div class="text-center">
      <h1 class="text-6xl font-bold text-red-500">
        {{ error.statusCode }}
      </h1>
      <p class="text-xl mt-4 text-gray-600">
        {{ error.statusMessage || 'Ein Fehler ist aufgetreten' }}
      </p>
      <button
        class="mt-8 px-6 py-3 bg-blue-500 text-white rounded-lg"
        @click="handleError"
      >
        Zurück zur Startseite
      </button>
    </div>
  </div>
</template>
⚠️

Kein Layout!

error.vue ist keine Seite im pages-Verzeichnis – sie ist eine eigenständige Komponente. Sie hat keinen Zugriff auf Layouts. Wenn du ein Layout brauchst, rendere es manuell innerhalb von error.vue.

Fehler auslösen: createError() & showError()

Mit createError() löst du Fehler aus, die Nuxt zur Fehlerseite weiterleiten. Das funktioniert sowohl auf dem Server als auch im Client:

pages/blog/[slug].vue
<script setup lang="ts">
const route = useRoute()
const { data: post } = await useFetch(`/api/blog/${route.params.slug}`)

// Wenn der Post nicht existiert → 404-Fehlerseite
if (!post.value) {
  throw createError({
    statusCode: 404,
    statusMessage: 'Artikel nicht gefunden',
    message: `Der Artikel "${route.params.slug}" existiert nicht.`
  })
}
</script>

showError() zeigt die Fehlerseite an, ohne den aktuellen Rendering-Prozess abzubrechen. Nützlich, wenn du den Fehler manuell behandeln willst:

composables/useApi.ts
// composables/useApi.ts
export function useApi() {
  async function fetchWithErrorHandling<T>(url: string): Promise<T | null> {
    try {
      const data = await $fetch<T>(url)
      return data
    } catch (err: any) {
      if (err.statusCode === 401) {
        // Zur Login-Seite weiterleiten
        await navigateTo('/login')
        return null
      }

      // Fehlerseite anzeigen
      showError({
        statusCode: err.statusCode || 500,
        statusMessage: err.message || 'Serverfehler'
      })
      return null
    }
  }

  return { fetchWithErrorHandling }
}
ℹ️

createError() wirft auf dem Server einen HTTP-Error (z.B. 404, 500). Im Client zeigt es die error.vue-Seite an. showError() zeigt immer nur die Client-seitige Fehlerseite.

NuxtErrorBoundary – Lokale Fehlerbehandlung

Nicht jeder Fehler soll die ganze Seite ersetzen. Mit NuxtErrorBoundary fängst du Fehler in einem bestimmten Bereich ab und zeigst eine Fallback-UI an:

pages/dashboard.vue
<template>
  <div class="dashboard">
    <h1>Dashboard</h1>

    <!-- Hauptinhalt lädt normal -->
    <DashboardStats />

    <!-- Widget-Fehler crashen nicht die ganze Seite -->
    <NuxtErrorBoundary>
      <WeatherWidget />

      <template #error="{ error, clearError }">
        <div class="p-4 bg-red-50 rounded-lg">
          <p class="text-red-600">Widget konnte nicht geladen werden</p>
          <p class="text-sm text-red-400">{{ error.message }}</p>
          <button
            class="mt-2 text-sm text-blue-500 underline"
            @click="clearError"
          >
            Erneut versuchen
          </button>
        </div>
      </template>
    </NuxtErrorBoundary>

    <!-- Weitere Widgets unabhängig voneinander -->
    <NuxtErrorBoundary>
      <ActivityFeed />
      <template #error="{ error }">
        <p class="text-gray-400">Aktivitäten nicht verfügbar</p>
      </template>
    </NuxtErrorBoundary>
  </div>
</template>
💡

Wann Error Boundaries nutzen?

Error Boundaries eignen sich für Bereiche, die unabhängig vom Rest der Seite funktionieren: Widgets, Sidebar-Inhalte, eingebettete Komponenten von Drittanbietern. Der Rest der Seite bleibt funktional, selbst wenn ein Widget fehlschlägt.

clearError() – Fehler zurücksetzen

clearError() setzt den aktuellen Fehlerzustand zurück und navigiert optional zu einer anderen Seite:

error.vue (Ausschnitt)
<script setup lang="ts">
const props = defineProps<{
  error: { statusCode: number; statusMessage?: string }
}>()

// Option 1: Fehler löschen und zur Startseite
const goHome = () => clearError({ redirect: '/' })

// Option 2: Fehler löschen und aktuelle Seite neu laden
const retry = () => clearError()

// Option 3: Zur vorherigen Seite
const goBack = () => {
  clearError()
  const router = useRouter()
  router.back()
}
</script>
⚠️

Wenn du clearError() ohne redirect aufrufst, stelle sicher, dass der Fehlerzustand wirklich behoben ist – sonst landest du in einer Endlosschleife.

API Error Handling

Fehler in API-Routen (server/api/) werden anders behandelt als Seitenfehler. Hier nutzt du createError aus dem H3-Framework:

server/api/produkte/[id].get.ts
// server/api/produkte/[id].get.ts
export default defineEventHandler(async (event) => {
  const id = getRouterParam(event, 'id')

  const produkt = await db.produkte.findUnique({
    where: { id: Number(id) }
  })

  // 404 – Nicht gefunden
  if (!produkt) {
    throw createError({
      statusCode: 404,
      statusMessage: `Produkt ${id} nicht gefunden`
    })
  }

  // 403 – Kein Zugriff
  if (!produkt.veroeffentlicht) {
    throw createError({
      statusCode: 403,
      statusMessage: 'Dieses Produkt ist nicht öffentlich'
    })
  }

  return produkt
})

Auf der Client-Seite fängst du API-Fehler mit useFetch ab:

pages/produkt/[id].vue
<script setup lang="ts">
const route = useRoute()

const { data: produkt, error } = await useFetch(
  `/api/produkte/${route.params.id}`
)

// Fehler an Nuxt weiterleiten
if (error.value) {
  throw createError({
    statusCode: error.value.statusCode,
    statusMessage: error.value.statusMessage
  })
}
</script>

<template>
  <div v-if="produkt">
    <h1>{{ produkt.name }}</h1>
    <p>{{ produkt.beschreibung }}</p>
  </div>
</template>
🛤️

Rails-Vergleich

In Rails nutzt du rescue_from ActiveRecord::RecordNotFound und render json: { error: '...' }, status: :not_found. In Nuxt nutzt du createError() in API-Routen – die HTTP-Semantik ist identisch.

Custom Error Pages pro Statuscode

Du kannst in deiner error.vue verschiedene Designs pro Fehlercode anzeigen:

error.vue
<script setup lang="ts">
const props = defineProps<{
  error: { statusCode: number; statusMessage?: string; message?: string }
}>()

const errorInfo = computed(() => {
  switch (props.error.statusCode) {
    case 404:
      return {
        emoji: '🔍',
        title: 'Seite nicht gefunden',
        text: 'Die gesuchte Seite existiert leider nicht.',
      }
    case 403:
      return {
        emoji: '🔒',
        title: 'Zugriff verweigert',
        text: 'Du hast keine Berechtigung für diese Seite.',
      }
    case 500:
      return {
        emoji: '💥',
        title: 'Serverfehler',
        text: 'Etwas ist schiefgelaufen. Wir arbeiten daran!',
      }
    default:
      return {
        emoji: '😵',
        title: 'Fehler',
        text: props.error.statusMessage || 'Ein unbekannter Fehler.',
      }
  }
})
</script>

<template>
  <div class="min-h-screen flex items-center justify-center">
    <div class="text-center max-w-md">
      <span class="text-8xl">{{ errorInfo.emoji }}</span>
      <h1 class="text-3xl font-bold mt-6">{{ errorInfo.title }}</h1>
      <p class="text-gray-500 mt-3">{{ errorInfo.text }}</p>
      <div class="mt-8 flex gap-4 justify-center">
        <button class="px-6 py-3 bg-gray-800 text-white rounded-lg" @click="clearError({ redirect: '/' })">
          Startseite
        </button>
        <button class="px-6 py-3 border rounded-lg" @click="clearError()">
          Erneut versuchen
        </button>
      </div>
    </div>
  </div>
</template>

Zusammenfassung

SituationNuxt-LösungRails-Äquivalent
Globale Fehlerseiteerror.vuepublic/404.html
Fehler werfencreateError()raise RecordNotFound
Fehler abfangenNuxtErrorBoundaryrescue_from
API-FehlercreateError() in server/apirender status: :not_found