Modul 9: Nuxt Fortgeschritten

Middleware

Intercepte Routen-Wechsel, schütze Seiten und führe Code vor jedem Seitenaufruf aus – ähnlich wie before_action in Rails-Controllern.

Was ist Middleware?

Middleware in Nuxt sind Funktionen, die vor dem Navigieren zu einer Route ausgeführt werden. Sie eignen sich perfekt für Authentifizierungs-Checks, Logging, Redirects oder das Laden von Daten, bevor eine Seite angezeigt wird.

Nuxt kennt drei Arten von Route-Middleware:

  • Named Middleware – in middleware/ definiert, explizit pro Seite zugewiesen
  • Global Middleware – mit .global-Suffix, läuft auf jeder Route
  • Inline Middleware – direkt in definePageMeta definiert
🛤️

Rails-Vergleich

In Rails nutzt du before_action in Controllern, um Code vor einer Action auszuführen. Nuxt Middleware funktioniert ähnlich – sie läuft vor dem Rendern der Seite. Der Unterschied: Rails-Filter sind an Controller gebunden, Nuxt Middleware an Routen.

Named Middleware

Named Middleware wird im middleware/-Verzeichnis definiert und kann dann einzelnen Seiten zugewiesen werden. Der Dateiname bestimmt den Namen der Middleware.

middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
  const { loggedIn } = useUserSession()

  // Wenn nicht eingeloggt, zur Login-Seite weiterleiten
  if (!loggedIn.value) {
    return navigateTo('/login')
  }
})

Um diese Middleware einer Seite zuzuweisen, referenzierst du sie in definePageMeta:

pages/dashboard.vue
<script setup lang="ts">
definePageMeta({
  middleware: 'auth'
})
</script>

<template>
  <div>
    <h1>Dashboard</h1>
    <p>Nur für eingeloggte Nutzer sichtbar</p>
  </div>
</template>
💡

Mehrere Middleware

Du kannst auch mehrere Middleware als Array angeben: middleware: ['auth', 'admin']. Sie werden in der angegebenen Reihenfolge ausgeführt.

Global Middleware

Global Middleware läuft auf jeder Route automatisch. Benenne die Datei einfach mit dem Suffix .global:

middleware/logging.global.ts
export default defineNuxtRouteMiddleware((to, from) => {
  console.log(`Navigation: ${from.path} → ${to.path}`)

  // Optional: Analytics-Event senden
  if (false) {
    trackPageView(to.path)
  }
})
ℹ️

Ausführungsreihenfolge

Globale Middleware wird immer vor named/inline Middleware ausgeführt. Innerhalb der globalen Middleware bestimmt die alphabetische Reihenfolge der Dateinamen die Ausführung. Nutze Zahlen-Präfixe wie 01.auth.global.ts für explizite Kontrolle.

Inline Middleware

Für einfache, seitenspezifische Logik kannst du Middleware direkt in definePageMeta definieren, ohne eine separate Datei zu erstellen:

pages/admin/settings.vue
<script setup lang="ts">
definePageMeta({
  middleware: [
    function (to, from) {
      const { isAdmin } = useUserSession()

      if (!isAdmin.value) {
        return abortNavigation()
      }
    }
  ]
})
</script>

<template>
  <div>
    <h1>Admin-Einstellungen</h1>
  </div>
</template>
⚠️

Einschränkung

Inline Middleware wird zur Build-Zeit extrahiert und kann deshalb keine lokalen Variablen oder Imports aus dem <script setup>-Block referenzieren. Für komplexere Logik nutze named Middleware.

Server Middleware

Neben Route-Middleware gibt es auch Server-Middleware, die auf dem Server bei jedem API-Request ausgeführt wird. Diese lebt im server/middleware/-Verzeichnis:

server/middleware/cors.ts
export default defineEventHandler((event) => {
  // CORS-Header für alle Server-Requests setzen
  setResponseHeaders(event, {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE',
    'Access-Control-Allow-Headers': 'Content-Type, Authorization',
  })
})
ℹ️

Server-Middleware ist etwas anderes als Route-Middleware: Sie läuft auf dem Server für jeden Request (auch API-Routen), nicht nur bei Navigation. Vergleichbar mit Rack-Middleware in Rails.

Praxisbeispiel: Auth Guard

Hier ein vollständiges Beispiel für eine Authentifizierungs-Middleware, die den Login-Status prüft und nicht-authentifizierte Nutzer weiterleitet:

middleware/auth.ts
export default defineNuxtRouteMiddleware(async (to) => {
  const { loggedIn, user, fetch: fetchSession } = useUserSession()

  // Session laden, falls noch nicht vorhanden
  if (!user.value) {
    await fetchSession()
  }

  // Öffentliche Routen überspringen
  const publicRoutes = ['/login', '/register', '/passwort-vergessen']
  if (publicRoutes.includes(to.path)) {
    return
  }

  // Nicht eingeloggt → Login-Seite
  if (!loggedIn.value) {
    return navigateTo('/login', {
      redirectCode: 302,
      // Ursprüngliche URL merken für Redirect nach Login
      query: { redirect: to.fullPath }
    })
  }
})
🛤️

Rails-Äquivalent

In Rails würdest du das so lösen:

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :authenticate_user!

  private

  def authenticate_user!
    unless current_user
      session[:return_to] = request.fullpath
      redirect_to login_path, alert: "Bitte einloggen"
    end
  end
end
💡

navigateTo vs. abortNavigation

navigateTo() leitet den Nutzer um (wie redirect_to in Rails). abortNavigation() bricht die Navigation komplett ab und kann optional einen Fehler werfen.

Zusammenfassung

TypOrtRails-Äquivalent
Namedmiddleware/name.tsbefore_action :name, only: [:show]
Globalmiddleware/name.global.tsbefore_action :name (ApplicationController)
InlinedefinePageMetaLambda in before_action
Serverserver/middleware/Rack-Middleware