DEV Community

coCo🙊
coCo🙊

Posted on

Your Navbar Should Know Where It Is

Most websites treat the navbar like a billboard. Same logo. Same links. Same everywhere. But what if your site tells a story? Shouldn't the navbar know which chapter we're in?

I've been building with Nuxt for a while now, and this pattern has become one of my go-to solutions for better UX. Figured it was worth sharing (and hey, what better way to start blogging than with something I actually use in production?).

If you've worked with React or vanilla projects, you know the pain: building different navigation states usually means either cramming tons of conditional logic into one component or creating separate navbar components and trying to keep them in sync. Route-based detection gets messy, auth state management becomes a nightmare, and you end up with navigation logic scattered everywhere.

Nuxt's layout system changes the game entirely.

Three different navbar states showing public marketing navbar with login button, minimal auth flow navbar, and full private dashboard navbar with user tools

Why Nuxt Makes This Pattern Shine

Here's where Nuxt really shines compared to other frameworks. In a typical React app, you'd probably:

  1. Route-based detection: Check useLocation() and conditionally render different navbars
  2. Auth state juggling: Subscribe to auth changes and update navigation accordingly
  3. Component duplication: Or worse, create separate navbar components that drift apart over time

All of that complexity disappears with Nuxt layouts. Instead of detecting context, you declare it:

<!-- layouts/default.vue - Public pages -->
<template>
  <div>
    <Navbar type="public" />
    <slot />
  </div>
</template>

<!-- layouts/auth.vue - Auth flow -->
<template>
  <div>
    <Navbar type="auth" />
    <slot />
  </div>
</template>

<!-- layouts/dashboard.vue - Private app -->
<template>
  <div>
    <Navbar type="private" />
    <slot />
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

No route detection. No auth state subscriptions. No complex conditional logic. Each layout simply tells the navbar what context it's in.

Code comparison showing complex React navbar logic with useLocation and useAuth hooks versus simple Nuxt layout approach with type props

The Framework Does the Heavy Lifting

When you navigate from /pricing (using default.vue) to /login (using auth.vue), Nuxt automatically switches layouts. Your navbar component doesn't need to know anything about routing or authentication - it just responds to the type prop that each layout provides.

This is declarative UI design at its finest. Instead of "figure out where you are and show the right thing," it becomes "each layout declares what it needs."

Three Nuxt layout files showing default.vue for public pages, auth.vue for login flows, and dashboard.vue for private app pages

Why Context-Aware Navigation Matters

Before we dive into the implementation, let's talk about why this pattern matters for UX:

Cognitive Load Reduction

Users shouldn't see dashboard buttons when they're not logged in. They shouldn't see marketing CTAs when they're already paying customers. Every irrelevant link is mental noise.

Clearer Mental Models

When the navbar adapts to context, it reinforces where users are in your app's journey. Public navbar = marketing mode. Auth navbar = focused mode. Private navbar = work mode.

Better Conversion

A marketing-focused navbar on public pages can improve sign-ups. A task-focused navbar in the app can improve feature adoption. One size doesn't fit all.

The navbar isn't just a static header — it's a narrator that changes tone depending on the scene.

User journey diagram showing progression from public pages with marketing focus, through auth flow with minimal distractions, to private app with full navigation

The Design: One Component, Many Faces

Instead of building separate navbars for each context, I use a single component with a type prop. In my Nuxt app, this gets embedded across different layouts:

<!-- layouts/default.vue -->
<template>
  <div>
    <Navbar type="public" />
    <slot />
  </div>
</template>

<!-- layouts/auth.vue -->
<template>
  <div>
    <Navbar type="auth" />
    <slot />
  </div>
</template>

<!-- layouts/dashboard.vue -->
<template>
  <div>
    <Navbar type="private" />
    <slot />
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

The magic happens inside the component. Same structure, different content based on which layout is calling it:

<template>
  <nav v-if="hydrated" class="navbar-base">
    <!-- Logo adapts to context -->
    <div v-if="type === 'public' || type === 'private'">
      <Logo :darkMode="isDarkMode" />
    </div>
    <div v-if="type === 'auth'">
      <CustomLogo />
    </div>

    <!-- Navigation content changes completely -->
    <div class="nav-actions">
      <!-- Public: Login button + language switcher -->
      <template v-if="type === 'public'">
        <LanguageSwitcher variant="secondary-2" />
        <NuxtLink to="/login">
          <BaseButton variant="secondary-2">Login</BaseButton>
        </NuxtLink>
      </template>

      <!-- Auth: Minimal, focused -->
      <template v-if="type === 'auth'">
        <LanguageSwitcher variant="primary-2" />
        <BaseButton v-if="authStore.isAuthenticated" @click="handleLogout">
          Logout
        </BaseButton>
      </template>

      <!-- Private: Full dashboard navigation -->
      <template v-if="type === 'private'">
        <DashboardToggle />
        <DashboardNav class="hidden lg:block" />
        <LanguageSwitcher />
        <BaseButton @click="handleLogout" class="hidden lg:block">
          Logout
        </BaseButton>
        <Profile />
        <!-- Mobile hamburger menu -->
        <MobileNavDropdown />
      </template>
    </div>
  </nav>
</template>
Enter fullscreen mode Exit fullscreen mode

The Technical Reality

This isn't just a nice design pattern - it comes with real technical considerations, especially in Nuxt.

Hydration Mismatch Protection

The v-if="hydrated" guard prevents that flash of wrong content during SSR:

<script setup>
const hydrated = ref(false);
onMounted(() => {
  hydrated.value = true;
});
</script>
Enter fullscreen mode Exit fullscreen mode

Without this, you might see the server-rendered navbar flicker into the client-rendered version. Not a great first impression.

Conditional Styling

Notice how the logo component gets different variants based on context. The auth flow uses CustomLogo while public/private use the regular Logo with dark mode detection:

const darkBackgroundRoutes = ['/', '/userType'];
const isDarkMode = computed(() => darkBackgroundRoutes.includes(route.path));
Enter fullscreen mode Exit fullscreen mode

Beyond the Code: Why This Matters

This pattern taught me something important about component design: smart UI doesn't just show what the user can do — it reflects where they are and where they're going.

When you build context-aware components, you're not just reducing code duplication. You're creating interfaces that feel more intuitive because they mirror the user's mental model of your app.

Maintainability

Want to add a new user role? Just extend the type prop and update the relevant layouts. Need to change the logo across all contexts? Edit one component. The navbar becomes a single source of truth instead of having separate navbar components scattered across your layouts folder.

Layout-Driven Context

Since each layout explicitly passes the type prop, you get perfect context awareness without complex routing logic. Your default.vue layout handles public pages, auth.vue handles authentication flows, and dashboard.vue handles the private app experience. The navbar just needs to know which layout is calling it.

Three benefit cards highlighting reduced cognitive load, better user experience, and easier maintenance of context-aware navigation

Extensibility

This pattern scales beautifully. I've extended it to handle admin panels, different user roles, and even A/B testing different navigation structures. The component grows with your app's complexity.

The Bigger Picture

Your navbar might seem like a small detail, but it's one of the few elements users see on every page. Making it context-aware sends a subtle but powerful message: "This app knows where you are and what you need."

Most users won't consciously notice a well-adapted navbar. But they'll definitely feel the difference between an interface that feels smart and one that feels like it's stuck in one gear.

What would your navbar say if it could talk? Mine would say: "Welcome" on the homepage, "Focus" during login, and "Let's work" in the dashboard.

What story is your navbar telling?


Have you implemented context-aware navigation in your apps? What challenges did you face? I'd love to hear about your approach in the comments.

Top comments (0)