useSelectedLayoutSegment()

Read the active route segment one level below the layout the hook is called from.

Signature
import { useSelectedLayoutSegment } from 'next/navigation'

const segment = useSelectedLayoutSegment(parallelRouteKey?: string)
  • Added in Next.js v13.0.0

Parameters

ParameterTypeDescription
$parallelRouteKeyoptionalstringName of a parallel route slot to read the active segment from, without the @ prefix — pass 'auth' to read the active segment of @auth.

Return value

string | nullThe active segment one level below the calling layout, or null when the layout's own page is the active route. Catch-all matches come back as one joined string: 'a/b/c' for /blog/a/b/c read from app/blog/layout.js.

Errors

Calling it in a Server Component errors; render it from a Client Component imported into the layout.

Changelog

  • PHP v13.0.0useSelectedLayoutSegment introduced.

useSelectedLayoutSegment answers one question: which child of this layout is currently showing? That makes it the precise tool for tab bars and sidebars that live in a layout, because it ignores everything deeper in the tree — on /dashboard/analytics/monthly, a component in app/dashboard/layout.js still gets 'analytics', so the Analytics tab stays highlighted on every page beneath it.

Compare that with usePathname, where you would hand-roll a startsWith check and get it subtly wrong for one route. Use the pathname when you need the whole URL; use this hook when the component's meaning is relative to its own layout.

What trips people up

  • It returns null for the layout's own page. In app/dashboard/layout.js on /dashboard, the value is null, not 'dashboard' — an overview tab has to test for null.
  • Layouts are Server Components by default, so the hook cannot live in layout.tsx itself. Put it in a small 'use client' component and import that.
  • Only one level down. For the full trail, use useSelectedLayoutSegments.
  • A catch-all route returns the matched path joined with slashes, not just its first segment.
  • With cacheComponents enabled the hook suspends on unknown dynamic params, so a tab bar in a parent layout needs a Suspense boundary or the build fails.

Example

Example · typescript
'use client'

import Link from 'next/link'
import { useSelectedLayoutSegment } from 'next/navigation'

export default function TabLink({
  segment,
  href,
  children,
}: {
  segment: string | null
  href: string
  children: React.ReactNode
}) {
  // In app/dashboard/layout.tsx:
  //   /dashboard                   -> null
  //   /dashboard/settings          -> 'settings'
  //   /dashboard/analytics/monthly -> 'analytics'
  const active = useSelectedLayoutSegment()

  return (
    <Link
      href={href}
      aria-current={active === segment ? 'page' : undefined}
      style={{ fontWeight: active === segment ? 'bold' : 'normal' }}
    >
      {children}
    </Link>
  )
}

When to use it

  • Bold the tab in a dashboard layout matching the open child route.
  • Keep a section highlighted across every page nested beneath it.
  • Read which page is open in an @modal parallel route slot.
  • Change a layout heading depending on the active child section.

Frequently asked questions

Why does useSelectedLayoutSegment return null?

Because the layout's own page is the active route — from app/dashboard/layout.js on /dashboard the value is null. It also returns null in the root layout on /.

What is the difference between useSelectedLayoutSegment and usePathname?

The segment hook returns just the one child segment below the calling layout, so it stays correct on deeper routes; usePathname returns the whole path and leaves the prefix matching to you.

Can I call useSelectedLayoutSegment inside layout.tsx?

Not directly, because layouts are Server Components by default. Put the hook in a small component with the 'use client' directive and import that component into the layout.

More examples

Wiring the tabs into a Server Component layout

Only TabLink crosses the client boundary; the layout itself stays on the server. Passing segment={null} for the overview tab is the idiomatic way to handle the layout's own index route, which the hook reports as null rather than 'dashboard'.

Example · typescript
// app/dashboard/layout.tsx — Server Component
import TabLink from './tab-link'

export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <section>
      <nav>
        {/* the overview tab is active when the segment is null */}
        <TabLink segment={null} href="/dashboard">
          Overview
        </TabLink>
        <TabLink segment="analytics" href="/dashboard/analytics">
          Analytics
        </TabLink>
        <TabLink segment="settings" href="/dashboard/settings">
          Settings
        </TabLink>
      </nav>
      {children}
    </section>
  )
}

Reading a parallel route slot

With no argument the hook reads the default children slot. Passing the slot name without its @ prefix reads that parallel route instead, which is how a layout can tell whether a modal or auth slot is currently filled and, for example, lock body scroll.

Example · typescript
'use client'

import { useSelectedLayoutSegment } from 'next/navigation'

// app/layout.tsx renders a @auth slot alongside children
export function AuthSlotStatus() {
  // Reads @auth, not the main children slot
  const authSegment = useSelectedLayoutSegment('auth')

  // /login  -> 'login'
  // /       -> null
  return authSegment ? <p>Auth flow: {authSegment}</p> : null
}

Active-link component from the official docs

Comparing the post slug against the segment needs no knowledge of the URL prefix, so the same component keeps working if /blog moves under a route group or a locale segment — something a usePathname comparison would break on.

Example · typescript
'use client'

import Link from 'next/link'
import { useSelectedLayoutSegment } from 'next/navigation'

// Imported into app/blog/layout.tsx
export default function BlogNavLink({
  slug,
  children,
}: {
  slug: string
  children: React.ReactNode
}) {
  // Navigating to /blog/hello-world returns 'hello-world'
  const segment = useSelectedLayoutSegment()
  const isActive = slug === segment

  return (
    <Link href={`/blog/${slug}`} style={{ fontWeight: isActive ? 'bold' : 'normal' }}>
      {children}
    </Link>
  )
}

Discussion

  • Be the first to comment on this lesson.