useSelectedLayoutSegment()
Read the active route segment one level below the layout the hook is called from.
import { useSelectedLayoutSegment } from 'next/navigation'
const segment = useSelectedLayoutSegment(parallelRouteKey?: string)- Added in Next.js v13.0.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$parallelRouteKeyoptional | string | Name 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
nullfor the layout's own page. Inapp/dashboard/layout.json/dashboard, the value isnull, not'dashboard'— an overview tab has to test fornull. - Layouts are Server Components by default, so the hook cannot live in
layout.tsxitself. 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
cacheComponentsenabled the hook suspends on unknown dynamic params, so a tab bar in a parent layout needs aSuspenseboundary or the build fails.
Example
'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'.
// 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.
'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.
'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