Skip to main content

What are layouts in Nuxt?

Layouts in Nuxt are reusable Vue templates that wrap page content with shared UI elements like headers, sidebars, or footers. The page renders inside the layout's <slot />.

Theory

TL;DR

  • A layout is the building shell: walls stay the same while the furniture (page content) changes per room.
  • Pages focus on content; layouts own the persistent frame around it.
  • If more than 2 pages share the same UI shell, a layout saves you from copy-pasting headers across files.
  • No layout specified? Nuxt falls back to layouts/default.vue automatically.

Quick example

vue
<!-- layouts/default.vue --> <template> <div class="app"> <nav>Global Nav</nav> <main> <slot /> <!-- page content renders here --> </main> <footer>© 2024</footer> </div> </template> <!-- pages/about.vue (no meta needed, default applies) --> <template> <p>About us</p> </template> <!-- Output: nav + "About us" + footer -->

The about.vue page never knows about the nav or footer. It renders its own content, and the layout wraps it.

Key difference

Layouts separate persistent UI from page-specific content via <slot />. A page like /about renders inside default.vue without any extra code. A page like /admin/orders opts into admin.vue with one line in definePageMeta. That is why pages stay slim and focused on their own data.

When to use

  • Shared header and footer on all pages: create layouts/default.vue.
  • Admin section with a sidebar: layouts/admin.vue + definePageMeta({ layout: 'admin' }) on each admin page.
  • Landing pages vs app pages: layouts/landing.vue for marketing, layouts/app.vue for authenticated users.
  • Page with no wrapper at all (fullscreen canvas, bare route): definePageMeta({ layout: false }).

How Nuxt resolves a layout

At build time, Nuxt scans layouts/ and registers each file by name. During navigation it checks definePageMeta on the target page. If nothing is set, it falls back to default. If default.vue does not exist, the page renders without any wrapper.

I once watched a developer delete default.vue thinking it was unused. Every page lost its nav immediately. Worth keeping in mind.

Common mistakes

1. Forgetting layout: false on bare pages. Any page without a layout setting gets default.vue applied. A fullscreen canvas or a bare route will automatically inherit the header and footer.

vue
<script setup> definePageMeta({ layout: false }) </script>

2. Setting layout in a variable instead of definePageMeta.

vue
<!-- Wrong: ignored at runtime --> <script setup> const layout = 'admin' // does nothing </script> <!-- Right --> <script setup> definePageMeta({ layout: 'admin' }) </script>

Only definePageMeta registers the layout choice. Variables in <script setup> are not read for this.

3. Putting <slot /> before the header.

vue
<!-- Wrong: page content renders above the nav --> <template> <slot /> <header>Nav</header> </template> <!-- Right --> <template> <header>Nav</header> <slot /> <footer>Footer</footer> </template>

Order in the template equals order on screen. Slot position is content position.

Real-world usage

  • Blog or docs site: layouts/docs.vue with a sidebar TOC for all /blog/* pages.
  • E-commerce checkout: layouts/checkout.vue with a progress bar, no main nav.
  • Admin panel: layouts/admin.vue with a drawer for all /admin/* routes.
  • Multi-tenant SaaS: different layout per tenant, switched via middleware.

Follow-up questions

Q: What happens if layouts/default.vue does not exist?
A: The page renders bare, with no wrapper. Nuxt does not throw an error.

Q: Can a layout access data from the page inside it?
A: Not directly. Use useState or useRoute() for state shared between layout and page.

Q: What is the difference between layout: false and layout: 'none'?
A: Both skip the layout. In Nuxt 3.6+, false is the preferred alias. Use it.

Q: How do you nest layouts?
A: Not natively supported. Use <NuxtLayout> inside a parent layout's <slot /> as a workaround.

Q: During SSR, if the layout's data fetch fails, what happens to the page?
A: The page renders inside the layout's error boundary. Use error.vue as a fallback layout, or wrap async layout data in <ClientOnly>.

Examples

Basic: default layout with no configuration

vue
<!-- layouts/default.vue --> <template> <div class="page-wrapper"> <header>My App</header> <slot /> <footer>My Footer</footer> </div> </template> <!-- pages/home.vue --> <template> <h1>Welcome home</h1> </template> <!-- Output: header + "Welcome home" + footer, no extra config needed -->

Every page picks up the default layout automatically. No definePageMeta required.

Intermediate: dashboard layout shared across admin pages

vue
<!-- layouts/dashboard.vue --> <template> <div class="dashboard"> <aside class="sidebar"> <NuxtLink to="/dashboard/orders">Orders</NuxtLink> <NuxtLink to="/dashboard/products">Products</NuxtLink> </aside> <main class="content"> <slot /> </main> </div> </template> <!-- pages/dashboard/orders.vue --> <script setup> definePageMeta({ layout: 'dashboard' }) const { data: orders } = await useFetch('/api/orders') </script> <template> <ul> <li v-for="order in orders" :key="order.id"> Order #{{ order.id }} - ${{ order.total }} </li> </ul> </template>

Both /dashboard/orders and /dashboard/products share the same sidebar. Navigating between them does not re-render the layout. Only the <slot /> content swaps.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?