Урок 04

Layouts

Что такое layout

Layout — обёртка вокруг страницы. Header, Footer, Sidebar — общие для группы страниц.

Пример:

  • Default layout — Header + Footer
  • Auth layout — центрированная форма
  • Admin layout — Sidebar + content

Создание

layouts/default.vue — применяется ко всем страницам по умолчанию:

<template>
    <div class="app">
        <AppHeader />
        <main>
            <slot />
        </main>
        <AppFooter />
    </div>
</template>

<slot /> — место для страницы.

app.vue

<template>
    <NuxtLayout>
        <NuxtPage />
    </NuxtLayout>
</template>

<NuxtLayout> — рендер выбранного layout.

Кастомный layout

layouts/auth.vue:

<template>
    <div class="auth-layout">
        <div class="auth-card">
            <slot />
        </div>
    </div>
</template>

<style scoped>
.auth-layout {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    background: #f5f5f5;
}
.auth-card {
    background: white;
    padding: 40px;
    border-radius: 12px;
    min-width: 400px;
}
</style>

Использование

Через definePageMeta:

<script setup lang="ts">
definePageMeta({
    layout: 'auth',
});
</script>

<template>
    <form>...</form>
</template>

Динамически:

<script setup lang="ts">
const layout = 'admin';
</script>

<template>
    <NuxtLayout :name="layout">
        <NuxtPage />
    </NuxtLayout>
</template>

Или через setPageLayout:

setPageLayout('admin');

Отключение layout

<script setup lang="ts">
definePageMeta({
    layout: false,
});
</script>

Страница рендерится без обёртки.

Несколько слотов

layouts/admin.vue:

<template>
    <div class="admin">
        <aside>
            <slot name="sidebar" />
        </aside>
        <main>
            <slot />
        </main>
    </div>
</template>

Страница:

<template>
    <template #sidebar>
        <AdminMenu />
    </template>

    <h1>Dashboard</h1>
</template>

Layout для группы страниц

Группы () позволяют задать layout для нескольких страниц без URL:

pages/
├── (marketing)/
│   ├── index.vue
│   ├── pricing.vue
│   └── about.vue
└── (app)/
    ├── dashboard.vue
    └── settings.vue

pages/(app).vue — определит layout для группы:

<script setup lang="ts">
definePageMeta({
    layout: 'app',
});
</script>

Получение route в layout

<script setup lang="ts">
const route = useRoute();
</script>

<template>
    <div>
        <nav>
            <NuxtLink
                v-for="link in links"
                :key="link.to"
                :to="link.to"
                :class="{ active: route.path === link.to }"
            >
                {{ link.label }}
            </NuxtLink>
        </nav>
        <slot />
    </div>
</template>

Переходы между layouts

<NuxtLayout name="default">
    <NuxtPage />
</NuxtLayout>

Анимация:

<NuxtLayout>
    <template #default>
        <Transition name="fade" mode="out-in">
            <NuxtPage />
        </Transition>
    </template>
</NuxtLayout>

Middleware для layouts

<script setup lang="ts">
definePageMeta({
    layout: 'admin',
    middleware: 'auth',
});
</script>

Практика — три layouts

layouts/default.vue:

<template>
    <div class="min-h-screen flex flex-col">
        <header>...</header>
        <main class="flex-1">
            <slot />
        </main>
        <footer>...</footer>
    </div>
</template>

layouts/auth.vue:

<template>
    <div class="min-h-screen grid place-items-center bg-gray-50">
        <div class="w-full max-w-md bg-white rounded-lg shadow p-8">
            <slot />
        </div>
    </div>
</template>

layouts/admin.vue:

<template>
    <div class="min-h-screen grid grid-cols-[240px_1fr]">
        <aside class="bg-gray-900 text-white p-6">
            <AdminMenu />
        </aside>
        <main class="p-8">
            <slot />
        </main>
    </div>
</template>

Итог

  • layouts/default.vue — по умолчанию
  • layouts/xxx.vue — кастомные
  • definePageMeta({ layout: 'xxx' }) — выбор
  • layout: false — отключение
  • <NuxtLayout> — в app.vue
  • <slot /> — место для страницы

Дальше — middleware.