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.
<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 — 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/.
<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><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:
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.erbLayout pro Seite wählen
Mit definePageMeta weist du jeder Seite ihr Layout zu. Ohne Angabe wird immer default verwendet.
<!-- 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.
<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.
<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
export default defineNuxtConfig({
app: {
// Transition beim Layout-Wechsel
layoutTransition: {
name: 'layout',
mode: 'out-in'
},
// Transition beim Seitenwechsel
pageTransition: {
name: 'page',
mode: 'out-in'
}
}
})/* 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:
# 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<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.