Type something to search...
Next.js useSelectedLayoutSegment

Next.js useSelectedLayoutSegment

Every layout in the App Router renders a children prop it doesn't fully control — the actual page or nested layout underneath it is decided by whatever route the user navigated to. Most of the time that's fine: the layout just wraps whatever shows up. But every so often you need the layout itself to react to which child segment is active — a tab bar that bolds the current tab, a sidebar link that highlights the current section, breadcrumbs that know where they are. useSelectedLayoutSegment exists for exactly that gap: it lets a Client Component nested inside a layout ask "which route segment directly below me is currently active?" without threading that information down as a prop from anywhere.

It's a small hook with a narrow job, but it solves a problem that's surprisingly awkward to solve any other way in a framework where layouts persist across navigations and don't automatically know what's rendering inside them.

The problem it solves

Picture a blog layout at app/blog/layout.tsx that renders a list of featured posts in a sidebar, and you want the currently-viewed post's link to render in bold. The layout is a Server Component — it doesn't re-render on client-side navigation, and it has no built-in way to ask "which post is the user looking at right now?" You could try to derive this from usePathname(), but that means manually parsing and matching path segments yourself, which gets fragile fast once you have nested dynamic segments, route groups, or parallel routes in the mix.

useSelectedLayoutSegment does that segment-matching for you, scoped precisely to "one level below the layout this hook is conceptually attached to."

Basic usage

"use client";

import { useSelectedLayoutSegment } from "next/navigation";

export default function ExampleClientComponent() {
  const segment = useSelectedLayoutSegment();

  return <p>Active segment: {segment}</p>;
}

Two things about this snippet matter more than they look like they do. First, 'use client' is not optional — this is a Client Component hook, full stop, because it depends on the client-side router's knowledge of the currently rendered route tree. Second, because layouts are Server Components by default, you'll almost never call this hook directly inside layout.tsx. The standard pattern is: write a small Client Component that calls the hook, then import and render that component from within the (Server Component) layout. This is the same "push the client boundary down to the smallest possible leaf" pattern you'd use for any interactive-but-otherwise-static piece of UI in the App Router.

What "one level below" actually means

The name is precise on purpose — this hook reads the segment immediately underneath wherever it's conceptually positioned in the tree, not the full remaining path. Here's the official mapping, and it's worth sitting with because the mental model matters more than any single example:

LayoutVisited URLReturned Segment
app/layout.js/null
app/layout.js/dashboard'dashboard'
app/dashboard/layout.js/dashboardnull
app/dashboard/layout.js/dashboard/settings'settings'
app/dashboard/layout.js/dashboard/analytics'analytics'
app/dashboard/layout.js/dashboard/analytics/monthly'analytics'

Notice the last row: even though the actual URL is three segments deep (dashboard/analytics/monthly), a component wired to app/dashboard/layout.js still only sees 'analytics' — the segment directly below that specific layout, not the leaf of the whole route. If you need every segment all the way down instead of just the next one, that's a different, related hook — useSelectedLayoutSegments (plural) — which returns an array of every active segment beneath the layout rather than just the first one. Reach for the singular version when you're building something like a tab bar where only the immediate child matters; reach for the plural version when you need full breadcrumb-style path awareness.

Catch-all routes behave a little differently. If you have app/blog/[...slug]/page.js and you're calling the hook from app/blog/layout.js, visiting /blog/a/b/c doesn't give you 'a' — it gives you the whole matched tail joined as a single string, 'a/b/c':

LayoutVisited URLReturned Segment
app/blog/layout.js/blog/a/b/c'a/b/c'

This makes sense once you remember that a catch-all segment is, structurally, one segment as far as the router's matching is concerned — it just happens to capture multiple path pieces.

Parameters and return value

const segment = useSelectedLayoutSegment(parallelRouteKey?: string)

The hook takes one optional argument: a parallelRouteKey. If you're not using Parallel Routes, you'll never pass this — omit it, and the hook reads the segment from the default (unnamed) slot. If your layout does define parallel route slots (folders named @analytics, @team, and so on), you pass the slot name as a string to read the active segment within that specific slot, since each parallel slot has its own independently active segment.

The return value is either a string (the active segment) or nullnull shows up at the root of a route (nothing is "below" the layout yet) or when there genuinely is no matching child segment for the layout in question.

A worked example: highlighting the active blog post

The docs' canonical example is a featured-posts sidebar, and it's worth walking through in full because it shows the complete pattern end to end — Server Component layout, extracted Client Component, and the hook doing the actual work.

// app/blog/blog-nav-link.tsx
"use client";

import Link from "next/link";
import { useSelectedLayoutSegment } from "next/navigation";

export default function BlogNavLink({
  slug,
  children,
}: {
  slug: string;
  children: React.ReactNode;
}) {
  const segment = useSelectedLayoutSegment();
  const isActive = slug === segment;

  return (
    <Link
      href={`/blog/${slug}`}
      style={{ fontWeight: isActive ? "bold" : "normal" }}
    >
      {children}
    </Link>
  );
}
// app/blog/layout.tsx
import { BlogNavLink } from "./blog-nav-link";
import getFeaturedPosts from "./get-featured-posts";

export default async function Layout({
  children,
}: {
  children: React.ReactNode;
}) {
  const featuredPosts = await getFeaturedPosts();
  return (
    <div>
      {featuredPosts.map((post) => (
        <div key={post.id}>
          <BlogNavLink slug={post.slug}>{post.title}</BlogNavLink>
        </div>
      ))}
      <div>{children}</div>
    </div>
  );
}

The layout itself stays a Server Component and can await data normally — it fetches the list of featured posts server-side, same as any other layout. Only the tiny bit of UI that actually needs to know "am I the active one?" gets pulled into its own client boundary. This is the shape you should aim for any time you reach for this hook: keep the client-side surface area as small as the interactivity genuinely requires, and let everything else stay a Server Component.

The Cache Components Suspense boundary gotcha

This is the part of the hook's behavior that trips people up in production, not in development, which makes it worse — everything looks fine locally and then the build fails or a route unexpectedly requires a loading state once deployed.

If your project has cacheComponents enabled, whether useSelectedLayoutSegment needs a Suspense boundary around it depends entirely on whether the active segment is knowable at build time:

  • Static routes, or dynamic routes fully covered by generateStaticParams: every possible segment value is known ahead of time, so the hook resolves during prerendering with no Suspense boundary required. This is the easy, boring case.
  • Dynamic routes with params not covered by generateStaticParams: those params are fallback params, unknown until an actual request comes in. Since the hook can't know the active segment during prerendering, it suspends — and if there's no Suspense boundary around it (or a parent), the build itself fails, not just the runtime.

The counterintuitive part: this can happen even when the component calling the hook is otherwise entirely static. A tab bar rendered once in a parent layout will suspend on any page beneath it that has an unresolved dynamic param, because from the router's perspective the tab bar's active-segment state depends on that unresolved value. The fix is to wrap the component that calls the hook (or a parent of it) in Suspense with a sensible fallback, so the rest of the layout can still prerender while that one piece defers to request time:

import { Suspense } from "react";
import { BlogNavLink } from "./blog-nav-link";

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <div>
      <Suspense fallback={<NavFallback />}>
        <BlogNavLink slug="hello-world">Hello World</BlogNavLink>
      </Suspense>
      <div>{children}</div>
    </div>
  );
}

If you hit this in the wild, Next.js's own error message points you at /docs/messages/blocking-prerender-client-hook for the full menu of fix options — but the short version is almost always "wrap it in Suspense, or make the dynamic param static via generateStaticParams."

Common mistakes

Calling the hook directly inside a layout.tsx file. This throws or misbehaves because layouts are Server Components and the hook is a client-only hook. Extract a small Client Component and import it into the layout instead — you can't skip this step.

Assuming it returns the full remaining path. If you need every segment from the layout down to the leaf (for breadcrumbs, for example), you want useSelectedLayoutSegments, not this hook. Reaching for the singular version and then trying to reconstruct a multi-level path from a single string is a sign you picked the wrong hook.

Forgetting the parallelRouteKey in a parallel-routes layout. If your layout has multiple named slots (@team, @analytics, etc.) and you call the hook with no argument, you'll get the segment for the default slot, which may not be the one you actually care about — pass the slot name explicitly.

Getting bitten by the Suspense requirement only in production. Local dev with next dev is far more forgiving about this than a real Cache-Components-enabled build. If a route works fine locally but fails to build in CI with a prerender error mentioning a client hook, this is very often the actual cause — go check whether the segment your tab bar depends on is fully covered by generateStaticParams.

Key takeaways

QuestionAnswer
What does it return?A string — the active route segment one level below the layout — or null
Where can it be called?Only in a Client Component ('use client'), typically one imported into a Server Component layout
How deep does it look?Exactly one segment down — for the full remaining path, use useSelectedLayoutSegments instead
What about catch-all routes?All matched segments are joined into a single string, e.g. 'a/b/c'
What about Parallel Routes?Pass the slot name as parallelRouteKey to read a specific slot's active segment
What can trip up a build?Cache Components + an unresolved dynamic param can force the hook to suspend — wrap it in Suspense

useSelectedLayoutSegment is a good example of a hook that looks almost too small to need documenting, right up until you try to hand-roll its behavior with usePathname() and string splitting. Reach for it any time a layout needs a small, focused piece of UI to know which of its immediate children is currently active — and reach for useSelectedLayoutSegments instead the moment "immediate child" isn't specific enough for what you're building.

Tags :
Share :

Related Posts

Can Next.js Be Used with GraphQL?

Can Next.js Be Used with GraphQL?

Next.js and GraphQL are two powerful technologies that have gained significant traction in the web development community. Next.js, a React-based fram

Dive Deeper
How does Next.js differ from Create React App?

How does Next.js differ from Create React App?

In the world of modern web development, React.js has emerged as a dominant force due to its flexibility, performance, and extensive ecosystem. Two po

Dive Deeper
How does Next.js handle image optimization?

How does Next.js handle image optimization?

In modern web development, image optimization plays a critical role in enhancing user experience and improving site performance. Large, unoptimized i

Dive Deeper