Vercel Logo

Layouts & Navigation

In Next.js, layouts are layout.tsx files that live next to the routes they wrap. You can nest them, and each one inherits from its parent. The mental model is inheritance: every route composes its layout from the chain of layout.tsx files above it in the folder tree.

Nuxt takes a named-slot approach instead. Layouts live in app/layouts/, and every page uses the default.vue layout unless it opts into a different one. There's no folder nesting, no implicit inheritance. A page picks its layout, and that's the end of the story.

The starter already has a basic layout. We're going to understand how it works, then update it so the navigation actually reflects the pages we've built.

Outcome

Build a shared layout with a header, navigation links, and a footer that wraps every page.

Fast Track

  1. Review app/layouts/default.vue and understand the <slot /> pattern
  2. Add navigation links to all public pages
  3. Verify the layout persists across page transitions

Hands-on exercise 1.4

Update the default layout to include navigation links to the browse page and a placeholder for auth links we'll add later.

Requirements:

  1. Review how app.vue connects to the layout system via <NuxtLayout> and <NuxtPage>
  2. Update app/layouts/default.vue with a header containing the site title and navigation links
  3. Include a Browse link that points to /springs
  4. Add a placeholder comment where auth links will go in Section 3
  5. Include a footer

Implementation hints:

  • <slot /> in a layout is where the page content renders. It's the equivalent of the {children} prop in a Next.js layout.tsx
  • <NuxtLayout> in app.vue tells Nuxt to use the layout system. <NuxtPage /> renders the matched page inside that layout
  • Every page gets the default layout automatically. You don't need to import it or specify it unless you want a different layout

Let's start with app.vue, the entry point:

app/app.vue
<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>
</template>

Two components, four lines. NuxtLayout finds the right layout (defaulting to default.vue), and NuxtPage renders the matched page inside it. In Next.js, this wiring is implicit. The framework handles it behind the scenes. In Nuxt, you opt in explicitly, which means you can also opt out. An app.vue without NuxtLayout would render pages with no layout at all.

Now the layout itself. Here's the React equivalent, for orientation:

// Next.js layout.tsx — for comparison
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <div>
      <header>
        <nav>
          <Link href="/">Hot Springs Finder</Link>
          <Link href="/springs">Browse</Link>
        </nav>
      </header>
      <main>{children}</main>
      <footer>...</footer>
    </div>
  );
}

And the Nuxt version:

app/layouts/default.vue
<template>
  <div>
    <header>
      <nav>
        <NuxtLink to="/">
          Hot Springs Finder
        </NuxtLink>
        <div>
          <NuxtLink to="/springs">
            Browse
          </NuxtLink>
          <!-- Auth links will go here -->
        </div>
      </nav>
    </header>
 
    <main>
      <slot />
    </main>
 
    <footer>
      Hot Springs Finder &mdash; Built with Nuxt
    </footer>
  </div>
</template>

The <slot /> inside the <main> tag is where the magic happens. Whatever page the router matches gets rendered there. In React terms, it's {children}. The difference is that Vue uses a dedicated element rather than a prop, which means you can have multiple named slots in a single layout. We won't need that for this project, but it's a pattern worth knowing exists.

Notice there's no <script> block yet. This layout is pure template. We'll add one in Section 3 when we need to check auth state and conditionally show navigation links.

Named layouts

Want a page with no nav? Create app/layouts/blank.vue and set definePageMeta({ layout: "blank" }) in the page. In Next.js, you'd need a separate route group with its own layout.tsx. Nuxt's approach is more explicit: the page declares what it wants.

Layout transitions

If you navigate between pages that use different layouts, Nuxt handles the swap automatically. But if your layouts have different DOM structures, the transition can feel jarring. Stick with the default layout for most pages and reach for alternatives sparingly.

Try It

Start the dev server and navigate between pages:

  1. Visit http://localhost:3000. The header should show "Hot Springs Finder" and a "Browse" link
  2. Click "Browse." The URL changes to /springs, the page content swaps, but the header and footer stay put
  3. Click "Hot Springs Finder" in the nav. You're back on the home page
  4. Visit /favorites, /visited, /login. All pages render inside the same layout

The layout never unmounts during navigation. The header and footer are stable. Only the content inside <slot /> changes.

Commit

git add -A && git commit -m "feat(layout): update default layout with navigation"

Done-When

  • The default layout renders a header with the site title and Browse link
  • Every page renders inside the layout with consistent header and footer
  • Navigation between pages doesn't cause a full page reload
  • You can explain how <slot /> in a Vue layout relates to {children} in a Next.js layout

Solution

app/app.vue
<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>
</template>
app/layouts/default.vue
<template>
  <div>
    <header>
      <nav>
        <NuxtLink to="/">
          Hot Springs Finder
        </NuxtLink>
        <div>
          <NuxtLink to="/springs">
            Browse
          </NuxtLink>
          <!-- Auth links will go here -->
        </div>
      </nav>
    </header>
 
    <main>
      <slot />
    </main>
 
    <footer>
      Hot Springs Finder &mdash; Built with Nuxt
    </footer>
  </div>
</template>