Modul 8: Nuxt Grundlagen

Layouts & Seiten

Layouts definieren die Grundstruktur deiner Seiten — Header, Footer, Sidebar. Wie application.html.erb in Rails, nur flexibler.

Was sind Layouts?

Ein Layout ist eine Vue-Komponente, die gemeinsame UI-Elemente für mehrere Seiten enthält — Navigation, Footer, Sidebar. Der Seiteninhalt wird über einen <slot /> eingesetzt.

In Rails kennst du das als application.html.erb mit <%= yield %>. In Nuxt ist das Konzept identisch, aber du kannst mehrere Layouts haben und sie pro Seite wählen.

🛤️

Rails-Vergleich: Layouts

Rails: app/views/layouts/application.html.erb<%= yield %>
Nuxt: layouts/default.vue<slot />
Rails: layout 'admin' im Controller
Nuxt: definePageMeta({ layout: 'admin' }) in der Seite

Das Default Layout

Das default.vue Layout wird automatisch für alle Seiten verwendet, die kein anderes Layout angeben — genau wie application.html.erb in Rails.

layouts/default.vue
<template>
  <div class="min-h-screen flex flex-col">
    <!-- Header / Navigation -->
    <header class="border-b px-6 py-4">
      <nav class="flex items-center gap-6">
        <NuxtLink to="/" class="text-xl font-bold">
          Meine App
        </NuxtLink>
        <NuxtLink to="/about">Über uns</NuxtLink>
        <NuxtLink to="/blog">Blog</NuxtLink>
        <NuxtLink to="/kontakt">Kontakt</NuxtLink>
      </nav>
    </header>

    <!-- Seiteninhalt wird hier eingesetzt -->
    <main class="flex-1 px-6 py-8">
      <slot />
    </main>

    <!-- Footer -->
    <footer class="border-t px-6 py-4 text-sm text-gray-500">
      © 2025 Meine App
    </footer>
  </div>
</template>

<!-- Vergleich mit Rails:
  app/views/layouts/application.html.erb
  <body>
    <%= render 'shared/header' %>
    <main><%= yield %></main>
    <%= render 'shared/footer' %>
  </body>
-->
app.vue (Root-Komponente)
<!-- app.vue — Die Root-Komponente -->
<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>
</template>

<!-- Rendering-Reihenfolge:
  app.vue
    → NuxtLayout (wählt aktives Layout)
      → layout/default.vue (oder benanntes Layout)
        → slot (= NuxtPage)
          → pages/xyz.vue (aktuelle Seite)
-->
ℹ️

app.vue vs. Layouts

app.vue ist die äußerste Hülle — sie enthält <NuxtLayout>, das wiederum das aktive Layout rendert. Innerhalb des Layouts rendert <slot /> die aktuelle Seite. In Rails wäre das: app.vue = das HTML-Dokument, layout = das ERB-Layout, page = die View.

Benannte Layouts

Für verschiedene Bereiche deiner App brauchst du verschiedene Layouts. Ein Admin-Bereich sieht anders aus als die öffentliche Seite. Erstelle einfach weitere Dateien in layouts/.

layouts/admin.vue
<template>
  <div class="flex min-h-screen">
    <!-- Sidebar -->
    <aside class="w-64 bg-gray-900 text-white p-4">
      <h2 class="text-lg font-bold mb-6">Admin</h2>
      <nav class="space-y-2">
        <NuxtLink to="/admin/dashboard"
          class="block px-3 py-2 rounded hover:bg-gray-800">
          Dashboard
        </NuxtLink>
        <NuxtLink to="/admin/users"
          class="block px-3 py-2 rounded hover:bg-gray-800">
          Benutzer
        </NuxtLink>
        <NuxtLink to="/admin/posts"
          class="block px-3 py-2 rounded hover:bg-gray-800">
          Beiträge
        </NuxtLink>
      </nav>
    </aside>

    <!-- Hauptbereich -->
    <main class="flex-1 p-8">
      <slot />
    </main>
  </div>
</template>
pages/admin/dashboard.vue
<template>
  <div>
    <h1 class="text-2xl font-bold mb-4">Admin Dashboard</h1>
    <p>Willkommen im Adminbereich!</p>
  </div>
</template>

<script setup lang="ts">
definePageMeta({
  layout: 'admin'  // ← Verwendet layouts/admin.vue
})
</script>
🛤️

Rails-Vergleich: Named Layouts

In Rails wählst du Layouts im Controller: class AdminController < ApplicationController; layout 'admin'; end. In Nuxt passiert das pro Seite mit definePageMeta. Der Vorteil: Jede Seite kann unabhängig ihr Layout wählen — nicht nur auf Controller-Ebene.

Mehrere Layouts im Projekt

Ein typisches Projekt hat oft drei bis vier Layouts:

Typische Layout-Struktur
layouts/
├── default.vue       # Öffentliche Seiten (Header + Footer)
├── admin.vue         # Admin-Bereich (Sidebar + Content)
├── auth.vue          # Login/Registrierung (zentriert, minimal)
└── blank.vue         # Kein Chrome (Druckansicht, Embeds)

# Rails-Äquivalent:
# app/views/layouts/
# ├── application.html.erb
# ├── admin.html.erb
# ├── auth.html.erb
# └── blank.html.erb

Layout pro Seite wählen

Mit definePageMeta weist du jeder Seite ihr Layout zu. Ohne Angabe wird immer default verwendet.

Layout-Zuweisung pro Seite
<!-- Verschiedene Seiten, verschiedene Layouts -->

<!-- pages/index.vue → default Layout (automatisch) -->
<script setup lang="ts">
// Kein definePageMeta nötig → nutzt 'default'
</script>

<!-- pages/admin/users.vue → admin Layout -->
<script setup lang="ts">
definePageMeta({ layout: 'admin' })
</script>

<!-- pages/login.vue → auth Layout -->
<script setup lang="ts">
definePageMeta({ layout: 'auth' })
</script>

<!-- pages/impressum.vue → blank Layout -->
<script setup lang="ts">
definePageMeta({ layout: 'blank' })
</script>

Layout deaktivieren

Manche Seiten brauchen gar kein Layout — z.B. eine Fullscreen-Login-Seite oder eine Druckansicht.

pages/login.vue (ohne Layout)
<template>
  <div class="flex items-center justify-center min-h-screen">
    <form class="w-full max-w-md p-8 rounded-xl border">
      <h1 class="text-2xl font-bold mb-6">Einloggen</h1>
      <input type="email" placeholder="E-Mail"
        class="w-full mb-4 px-4 py-2 rounded border" />
      <input type="password" placeholder="Passwort"
        class="w-full mb-4 px-4 py-2 rounded border" />
      <button class="w-full py-2 bg-blue-600 text-white rounded">
        Anmelden
      </button>
    </form>
  </div>
</template>

<script setup lang="ts">
definePageMeta({
  layout: false  // ← Kein Layout — die Seite rendert allein
})
</script>

Dynamische Layout-Wechsel

Du kannst das Layout auch zur Laufzeit wechseln — z.B. basierend auf dem Login-Status oder einer Benutzereinstellung.

Dynamisches Layout
<template>
  <div>
    <h1>Profil</h1>
    <button @click="toggleLayout">
      Layout wechseln
    </button>
  </div>
</template>

<script setup lang="ts">
definePageMeta({ layout: 'default' })

function toggleLayout() {
  // Layout zur Laufzeit wechseln
  setPageLayout('admin')
}
</script>
⚠️

Reaktivität beachten

definePageMeta wird zur Build-Zeit ausgewertet und ist nicht reaktiv. Für dynamische Layouts musst du setPageLayout() verwenden, nicht definePageMeta.

Layout-Transitions

Beim Wechsel zwischen Layouts kannst du Animationen einsetzen. Das gibt deiner App ein poliertes Gefühl — etwas, das in klassischen Rails-Apps mit Turbo nur schwer erreichbar ist.

nuxt.config.ts
// nuxt.config.ts
export default defineNuxtConfig({
  app: {
    // Transition beim Layout-Wechsel
    layoutTransition: {
      name: 'layout',
      mode: 'out-in'
    },
    // Transition beim Seitenwechsel
    pageTransition: {
      name: 'page',
      mode: 'out-in'
    }
  }
})
assets/css/transitions.css
/* Layout-Transition */
.layout-enter-active,
.layout-leave-active {
  transition: opacity 0.3s ease;
}

.layout-enter-from,
.layout-leave-to {
  opacity: 0;
}

/* Page-Transition */
.page-enter-active,
.page-leave-active {
  transition: all 0.2s ease;
}

.page-enter-from {
  opacity: 0;
  transform: translateY(10px);
}

.page-leave-to {
  opacity: 0;
  transform: translateY(-10px);
}
💡

Page Transitions vs. Layout Transitions

Page Transition: Animation beim Seitenwechsel innerhalb desselben Layouts.
Layout Transition: Animation beim Wechsel zwischen verschiedenen Layouts.
Beide können unabhängig konfiguriert werden.

Komplett-Beispiel: App mit drei Layouts

So sieht eine typische Nuxt-App mit mehreren Layouts und zugeordneten Seiten aus:

Projekt-Struktur
# Typische App-Struktur mit mehreren Layouts:

layouts/
├── default.vue         # Navigation + Footer
├── admin.vue           # Sidebar-Layout
└── auth.vue            # Zentriert, minimal

pages/
├── index.vue           # → default Layout
├── about.vue           # → default Layout
├── login.vue           # → auth Layout
├── register.vue        # → auth Layout
└── admin/
    ├── dashboard.vue   # → admin Layout
    ├── users.vue       # → admin Layout
    └── settings.vue    # → admin Layout
layouts/default.vue
<template>
  <div class="min-h-screen flex flex-col bg-gray-50">
    <header class="bg-white shadow-sm">
      <div class="max-w-7xl mx-auto px-4 py-4 flex justify-between">
        <NuxtLink to="/" class="text-xl font-bold text-gray-900">
          MeineApp
        </NuxtLink>

        <nav class="flex items-center gap-4">
          <NuxtLink to="/about"
            class="text-gray-600 hover:text-gray-900">
            Über uns
          </NuxtLink>
          <NuxtLink to="/blog"
            class="text-gray-600 hover:text-gray-900">
            Blog
          </NuxtLink>

          <!-- Login/Logout je nach Status -->
          <NuxtLink v-if="!isLoggedIn" to="/login"
            class="px-4 py-2 bg-blue-600 text-white rounded-lg">
            Einloggen
          </NuxtLink>
          <NuxtLink v-else to="/admin/dashboard"
            class="px-4 py-2 bg-gray-200 rounded-lg">
            Dashboard
          </NuxtLink>
        </nav>
      </div>
    </header>

    <main class="flex-1 max-w-7xl mx-auto w-full px-4 py-8">
      <slot />
    </main>

    <footer class="bg-white border-t py-6 text-center text-sm text-gray-500">
      © 2025 MeineApp — Gebaut mit Nuxt 3
    </footer>
  </div>
</template>

<script setup lang="ts">
const { isLoggedIn } = useAuth()
</script>
💡

Nächster Schritt

Jetzt kannst du Seiten strukturieren und mit Layouts versehen. Als Nächstes lernen wir, wie du Daten vom Server lädst — mit useFetch und useAsyncData.