4. Layouts
🖼️ Layouts в Nuxt 3
Заголовок раздела «🖼️ Layouts в Nuxt 3»Layouts — это обёртки для твоих страниц. Header, Footer, Sidebar — всё, что повторяется на множестве страниц, живёт в layout. Меняешь layout — меняется обёртка всех использующих его страниц.
Зачем нужны Layouts? 🤔
Заголовок раздела «Зачем нужны Layouts? 🤔»Без layouts каждая страница была бы:
<!-- pages/about.vue — БЕЗ layouts --><template> <div> <!-- Дублирование на каждой странице! --> <header> <nav>...</nav> </header>
<!-- Уникальный контент --> <main>О нас</main>
<footer>...</footer> </div></template>С layouts:
<!-- pages/about.vue — С layouts --><template> <!-- Только уникальный контент! --> <main>О нас</main></template>Дефолтный Layout 🏠
Заголовок раздела «Дефолтный Layout 🏠»Создаём layouts/default.vue:
<template> <div class="app-wrapper"> <AppHeader />
<!-- Слот для контента страницы --> <main class="main-content"> <slot /> </main>
<AppFooter /> </div></template>
<style scoped>.app-wrapper { display: flex; flex-direction: column; min-height: 100vh;}.main-content { flex: 1; padding: 2rem; max-width: 1200px; margin: 0 auto; width: 100%;}</style>Этот layout автоматически применяется ко всем страницам.
Подключение NuxtLayout в app.vue 🔌
Заголовок раздела «Подключение NuxtLayout в app.vue 🔌»<template> <NuxtLayout> <!-- NuxtPage рендерится внутри <slot> layout --> <NuxtPage /> </NuxtLayout></template>Кастомные Layouts 🎨
Заголовок раздела «Кастомные Layouts 🎨»Создаём несколько layouts для разных страниц:
layouts/├── default.vue ← Дефолтный (применяется везде)├── admin.vue ← Для админ-панели├── auth.vue ← Для страниц входа/регистрации├── blog.vue ← Для блога└── blank.vue ← Чистый (без header/footer)<template> <div class="admin-layout"> <AdminSidebar /> <div class="admin-content"> <AdminHeader /> <main> <slot /> </main> </div> </div></template><template> <div class="auth-layout"> <div class="auth-card"> <NuxtLink to="/"> <img src="/logo.svg" alt="Logo" /> </NuxtLink> <slot /> </div> </div></template>Выбор Layout для страницы 📋
Заголовок раздела «Выбор Layout для страницы 📋»Через definePageMeta
Заголовок раздела «Через definePageMeta»<script setup>definePageMeta({ layout: 'admin'})</script><script setup>definePageMeta({ layout: 'auth'})</script><!-- pages/landing.vue — без layout --><script setup>definePageMeta({ layout: false})</script>
<template> <!-- Полностью кастомная страница без обёртки --> <div class="landing">...</div></template>Динамический Layout
Заголовок раздела «Динамический Layout»<script setup>const { data: user } = await useFetch('/api/auth/me')
// Меняем layout динамическиdefinePageMeta({ layout: 'default'})
// Или через useRouteconst layout = computed(() => user.value?.isAdmin ? 'admin' : 'default')</script>Или через NuxtLayout с пропом:
<script setup>const route = useRoute()// Берём layout из мета-данных маршрутаconst layout = computed(() => route.meta.layout || 'default')</script>
<template> <NuxtLayout :name="layout"> <NuxtPage /> </NuxtLayout></template>Именованные Слоты в Layouts 🎰
Заголовок раздела «Именованные Слоты в Layouts 🎰»Layouts поддерживают несколько слотов:
<template> <div class="blog-layout"> <header> <slot name="header"> <!-- Дефолтный header если не передан --> <h1>Блог</h1> </slot> </header>
<div class="blog-content"> <main> <!-- Основной контент --> <slot /> </main> <aside> <slot name="sidebar"> <!-- Дефолтный sidebar --> <BlogSidebar /> </slot> </aside> </div> </div></template><script setup>definePageMeta({ layout: 'blog' })</script>
<template> <!-- Передаём контент в именованные слоты --> <template #header> <h1>{{ post.title }}</h1> <p>{{ post.description }}</p> </template>
<!-- Основной контент (в default slot) --> <article> {{ post.content }} </article>
<!-- Кастомный sidebar --> <template #sidebar> <RelatedPosts :posts="relatedPosts" /> </template></template>Layout Transitions 🎭
Заголовок раздела «Layout Transitions 🎭»<template> <div> <AppHeader /> <slot /> <AppFooter /> </div></template>export default defineNuxtConfig({ app: { // Переход для layout layoutTransition: { name: 'layout', mode: 'out-in' }, // Переход для страниц pageTransition: { name: 'page', mode: 'out-in' } }})/* Layout переходы */.layout-enter-active,.layout-leave-active { transition: all 0.4s ease;}.layout-enter-from { opacity: 0; transform: translateX(-20px);}.layout-leave-to { opacity: 0; transform: translateX(20px);}
/* Page переходы */.page-enter-active,.page-leave-active { transition: all 0.3s ease;}.page-enter-from,.page-leave-to { opacity: 0; transform: translateY(10px);}Кастомный переход на конкретной странице:
<script setup>definePageMeta({ layout: 'admin', layoutTransition: { name: 'slide-left' }, pageTransition: { name: 'fade' }})</script>Error Layout — обработка ошибок 🚨
Заголовок раздела «Error Layout — обработка ошибок 🚨»<!-- error.vue — специальный файл (не в layouts/) --><script setup lang="ts">const props = defineProps<{ error: { statusCode: number statusMessage: string message: string }}>()
// Перезагрузить приложениеconst handleError = () => clearError({ redirect: '/' })</script>
<template> <div class="error-page"> <h1>{{ error.statusCode }}</h1> <p>{{ error.statusMessage }}</p> <button @click="handleError">На главную</button> </div></template>NuxtLayout — программное управление 🎛️
Заголовок раздела «NuxtLayout — программное управление 🎛️»<!-- Условный рендеринг layout --><template> <NuxtLayout :name="showHeader ? 'default' : 'blank'"> <NuxtPage /> </NuxtLayout></template><!-- Передача пропсов в layout --><template> <NuxtLayout name="admin" :sidebar-collapsed="sidebarCollapsed"> <NuxtPage /> </NuxtLayout></template><!-- layouts/admin.vue — принимаем проп --><script setup>const props = defineProps<{ sidebarCollapsed?: boolean}>()</script>
<template> <div :class="{ 'sidebar-collapsed': sidebarCollapsed }"> <slot /> </div></template>Лучшие практики Layouts 💡
Заголовок раздела «Лучшие практики Layouts 💡»✅ DO:- Один дефолтный layout для большинства страниц- Отдельный layout для auth (без навигации)- Отдельный layout для admin (с sidebar)- Используй именованные слоты для гибкости- Держи layouts простыми — только обёртка
❌ DON'T:- Не дублируй логику между layouts- Не делай layout слишком сложным- Не помещай бизнес-логику в layout- Не забывай про <slot /> — без него контент не отобразитсяРеальный пример: Многоуровневое приложение 🏗️
Заголовок раздела «Реальный пример: Многоуровневое приложение 🏗️»layouts/├── default.vue ← Публичный сайт│ └── Header + Footer├── auth.vue ← Авторизация│ └── Центрированная форма└── admin.vue ← Административная панель └── Sidebar + Top bar
pages/├── index.vue → layout: 'default'├── about.vue → layout: 'default'├── login.vue → layout: 'auth'├── register.vue → layout: 'auth'└── admin/ ├── dashboard.vue → layout: 'admin' ├── users.vue → layout: 'admin' └── settings.vue → layout: 'admin'