Layouts in Nuxt
Sharing a page shell — header, nav, footer — across routes without repeating markup in every page.
읽는 데 2분
Most sites share a visual shell across pages — a header and nav at the top, a footer at the bottom — with only the middle changing. Nuxt's layouts/ directory captures that shell once, instead of repeating it in every file under pages/.
Creating a default layout
A layout is a Vue component with a <slot /> marking where the page content goes:
<!-- layouts/default.vue -->
<template>
<div class="app-shell">
<header>
<NuxtLink to="/">My Site</NuxtLink>
<NuxtLink to="/about">About</NuxtLink>
</header>
<main>
<slot />
</main>
<footer>© 2026 My Site</footer>
</div>
</template>A layout file named default.vue is applied automatically to every page that doesn't specify a different one — you don't have to opt individual pages into it.
Opting a page into a different layout
Some pages need a different shell — an admin area without the public nav, or a full-bleed landing page with no chrome at all. definePageMeta (auto-imported, usable only inside a page component) declares which layout a page uses:
<!-- pages/admin/dashboard.vue -->
<script setup>
definePageMeta({
layout: "admin",
});
</script>
<template>
<h1>Dashboard</h1>
</template><!-- layouts/admin.vue -->
<template>
<div class="admin-shell">
<aside><!-- admin sidebar --></aside>
<main><slot /></main>
</div>
</template>Nuxt matches the layout name to a file in layouts/ by filename, the same way pages/ matches URLs by path. Setting layout: false opts a page out of layouts entirely, rendering only the page's own template.
Why not just put the header/footer in app.vue?
You could put shared markup directly in app.vue and skip layouts, but that only works if every page wants the exact same shell. The moment one section of the site — an admin panel, an auth flow, a marketing landing page — needs a different frame, a single shared shell in app.vue forces you to add conditionals inside it. Layouts keep that decision declarative and colocated with each page (definePageMeta({ layout: "admin" })) rather than as branching logic in a root component that every route has to pass through.
app.vue still has a job in a layout-based app: it just renders <NuxtLayout><NuxtPage /></NuxtLayout>, letting NuxtLayout pick the right shell and NuxtPage render the matched route inside it.