layout.js

Wraps a segment and its children in shared UI that survives navigation without re-rendering.

Signature
export default function Layout({
  children,
  params,
}: {
  children: React.ReactNode
  params: Promise<{ team: string }>
}): React.ReactNode
  • Added in Next.js v13.0.0

Parameters

ParameterTypeDescription
$childrenReact.ReactNodeThe route segments this layout wraps — usually a child layout or page, but also loading, error and not-found UI when they are active.
$paramsoptionalPromise<{ [key: string]: string | string[] }>Resolves to the dynamic route parameters from the root segment down to this layout. Segments below the layout are not included.

Return value

React.ReactNode | Promise<React.ReactNode>The wrapping UI for the segment. It must render the children prop somewhere in the tree or the routes below it will not appear.

Errors

forbidden() cannot be called in the root layout. An app directory without a root layout is a build error, and a root layout that omits <html> or <body> is invalid.

Changelog

  • PHP v15.0.0-RCparams became a Promise. Synchronous access still works in 15 for backwards compatibility but is deprecated; a codemod is available.
  • PHP v13.0.0layout introduced.

A layout is the outermost component of a route segment: it wraps template.js, error.js, loading.js, not-found.js and page.js below it. Its defining property is that it does not re-render on navigation — move between two pages under the same layout and the sidebar keeps its scroll position, the audio element keeps playing, and client state survives.

The app directory must contain a root layout, and that one is the only place <html> and <body> belong. You can have several root layouts (via route groups, or by omitting app/layout.js), but navigating between them forces a full page load rather than a client-side transition.

What trips people up

  • No searchParams, no pathname. Because layouts do not re-render, request-varying values would go stale, so they are not available. Use the page's searchParams prop, or usePathname / useSearchParams in a Client Component child.
  • A slow layout blocks navigation. loading.js sits below the layout and cannot cover it, so an uncached fetch or a cookies() call in the layout stalls the transition. Wrap that work in its own <Suspense>, or move it into the page.
  • Layouts cannot pass data to children. Fetch in both places and let fetch dedupe, or wrap the loader in React's cache.
  • Never hand-write <title> or <meta> here — use the Metadata API.

Example

Example · typescript
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

When to use it

  • Put a persistent dashboard sidebar around every route under /dashboard
  • Define the root <html> and <body> plus global fonts and stylesheets once
  • Render a team header from the [team] dynamic segment above all of that team's pages
  • Keep a media player or chat widget mounted while the user navigates between pages

Frequently asked questions

Why does my layout not re-render when I navigate?

That is intentional — layouts are cached on the client and reused across navigations so shared UI keeps its state and expensive work is not repeated. If you need a component that remounts on every navigation, use template.js instead; if you only need a fresh value like the pathname, read it in a Client Component child.

How do I get searchParams in a layout?

You cannot — layouts do not re-render, so query params would become stale and Next.js does not pass them. Read the searchParams prop in page.js and pass the values into your layout's children, or call the useSearchParams hook inside a Client Component rendered by the layout.

Can I have more than one root layout in Next.js?

Yes. Any layout with no layout.js above it is a root layout, which you get either with route groups such as app/(shop)/layout.js and app/(marketing)/layout.js, or by omitting app/layout.js entirely. Each must render its own <html> and <body>, and navigating between two root layouts triggers a full page load rather than a client-side transition.

More examples

Reading a dynamic segment in a nested layout

At app/dashboard/[team]/layout.tsx the URL /dashboard/acme resolves params to { team: 'acme' } and the header renders once. Navigating from /dashboard/acme/billing to /dashboard/acme/settings does not re-run this component — but switching to /dashboard/other does, because the param it depends on changed.

Example · typescript
export default async function DashboardLayout({
  children,
  params,
}: {
  children: React.ReactNode
  params: Promise<{ team: string }>
}) {
  const { team } = await params

  return (
    <section>
      <header>
        <h1>Welcome to {team}'s Dashboard</h1>
      </header>
      <main>{children}</main>
    </section>
  )
}

Keeping navigation instant despite slow layout data

DashboardNav is the component that fetches. Because loading.js cannot show a fallback for work done inside the layout, the uncached read is given its own Suspense boundary: the shell and the page stream immediately and the nav swaps in when its data resolves. Without the boundary the whole navigation would block on DashboardNav.

Example · typescript
import { Suspense } from 'react'
import { NavSkeleton } from './nav-skeleton'
import { DashboardNav } from './dashboard-nav'

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <>
      <Suspense fallback={<NavSkeleton />}>
        <DashboardNav />
      </Suspense>
      <main>{children}</main>
    </>
  )
}

Highlighting the active link from a layout

This lives in app/ui/nav-links.tsx and is imported by the layout. The layout itself has no access to the pathname because it never re-renders; the Client Component does re-render on navigation, so the active class stays correct as the user moves around.

Example · typescript
'use client'

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

export function NavLinks() {
  const pathname = usePathname()

  return (
    <nav>
      <Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">
        Home
      </Link>
      <Link
        className={`link ${pathname === '/about' ? 'active' : ''}`}
        href="/about"
      >
        About
      </Link>
    </nav>
  )
}

Discussion

  • Be the first to comment on this lesson.