Fix: NuxtLayout Needs Single Root Node

Error message:
[nuxt] `<NuxtLayout>` needs to be passed a single root node in its default slot.
layouts 2025-01-25

What Causes This Error?

This error occurs when your layout or page component has multiple root elements. Vue 3 allows multiple root nodes (fragments), but Nuxt layouts require a single root node to properly handle transitions and hydration.

The Problem

Multiple Root Elements in Layout

<!-- ❌ Wrong - layouts/default.vue -->
<template>
  <Header />
  <main>
    <slot />
  </main>
  <Footer />
</template>

Multiple Root Elements in Page

<!-- ❌ Wrong - pages/index.vue -->
<template>
  <h1>Title</h1>
  <p>Content</p>
  <Footer />
</template>

The Fix

Wrap Everything in a Single Element

<!-- ✅ Correct - layouts/default.vue -->
<template>
  <div>
    <Header />
    <main>
      <slot />
    </main>
    <Footer />
  </div>
</template>
<!-- ✅ Correct - pages/index.vue -->
<template>
  <div>
    <h1>Title</h1>
    <p>Content</p>
    <Footer />
  </div>
</template>

Common Scenarios

Layout with Teleport

<!-- ❌ Wrong - teleport counts as root node -->
<template>
  <div class="layout">
    <slot />
  </div>
  <Teleport to="body">
    <Modal />
  </Teleport>
</template>

<!-- ✅ Correct - wrap everything -->
<template>
  <div>
    <div class="layout">
      <slot />
    </div>
    <Teleport to="body">
      <Modal />
    </Teleport>
  </div>
</template>

Conditional Root Elements

<!-- ❌ Wrong - v-if creates multiple potential roots -->
<template>
  <Loading v-if="loading" />
  <Content v-else />
</template>

<!-- ✅ Correct - single wrapper -->
<template>
  <div>
    <Loading v-if="loading" />
    <Content v-else />
  </div>
</template>

Page with Comments

<!-- ❌ Wrong - HTML comments count as nodes -->
<template>
  <!-- Page Header -->
  <Header />
  <main>Content</main>
</template>

<!-- ✅ Correct - comments inside wrapper -->
<template>
  <div>
    <!-- Page Header -->
    <Header />
    <main>Content</main>
  </div>
</template>

Why Single Root Is Required

  1. Transitions - Nuxt page transitions need a single element to animate
  2. Hydration - SSR hydration expects matching DOM structure
  3. Layout system - NuxtLayout injects content into a specific slot
  4. Keep-alive - Vue’s keep-alive requires single root components

Using Semantic HTML

You don’t have to use <div>. Any single wrapper works:

<!-- ✅ Using main as root -->
<template>
  <main class="layout">
    <Header />
    <slot />
    <Footer />
  </main>
</template>

<!-- ✅ Using section as root -->
<template>
  <section class="page">
    <h1>Title</h1>
    <p>Content</p>
  </section>
</template>

<!-- ✅ Using article as root -->
<template>
  <article>
    <header>Post Title</header>
    <p>Post content</p>
  </article>
</template>

Similar warning for layouts:

[nuxt] `{name}` layout does not have a single root node and will cause errors when navigating between routes.

Same fix applies—wrap all layout content in a single element.

[nuxt] `{filename}` does not have a single root node and will cause errors when navigating between routes.

This warns that page navigation transitions will fail.

Quick Fix Pattern

If you have:

<template>
  <A />
  <B />
  <C />
</template>

Change to:

<template>
  <div>
    <A />
    <B />
    <C />
  </div>
</template>

Debugging Hydration Issues

If you’re still getting hydration mismatches:

// nuxt.config.ts
export default defineNuxtConfig({
  vue: {
    compilerOptions: {
      comments: false // Remove HTML comments that might cause issues
    }
  }
})

Quick Checklist

  • Layout has single root element wrapping <slot />
  • Page has single root element wrapping all content
  • Teleports are inside the root wrapper
  • v-if/v-else blocks are inside a wrapper
  • No stray HTML comments outside wrapper
  • No text nodes outside wrapper