layout.js

layout ファイルは、Next.js アプリケーションでレイアウトを定義するために使用されます。

export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return <section>{children}</section>
}
export default function DashboardLayout({ children }) {
  return <section>{children}</section>
}

ルートレイアウト は、ルート app ディレクトリ内の最上位のレイアウトです。<html> タグや <body> タグ、その他のグローバルに共有される UI を定義するために使用されます。

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

リファレンス

Props

children (必須)

レイアウトコンポーネントは children プロップを受け取り、使用する必要があります。レンダリング時、children にはレイアウトがラップするルートセグメントが含まれます。これには主に子の Layout または Page のコンポーネントが含まれますが、LoadingError などの特別なファイルも含まれる場合があります。

params (任意)

ルートセグメントからそのレイアウトまでの 動的ルートパラメータ オブジェクトを含む Promise です。

export default async function Layout({
  params,
}: {
  params: Promise<{ team: string }>
}) {
  const { team } = await params
}
export default async function Layout({ params }) {
  const { team } = await params
}
例のルートURLparams
app/dashboard/[team]/layout.js/dashboard/1Promise<{ team: '1' }>
app/shop/[tag]/[item]/layout.js/shop/1/2Promise<{ tag: '1', item: '2' }>
app/blog/[...slug]/layout.js/blog/1/2Promise<{ slug: ['1', '2'] }>
  • params プロップは Promise です。値を取得するには async/await または React の use 関数を使用する必要があります。
    • バージョン14以前では、params は同期プロップでした。後方互換性のために、Next.js 15でも同期的にアクセスできますが、この動作は将来非推奨になります。

ルートレイアウト

app ディレクトリには、必ずルート app/layout.js を含める必要があります

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html>
      <body>{children}</body>
    </html>
  )
}
export default function RootLayout({ children }) {
  return (
    <html>
      <body>{children}</body>
    </html>
  )
}
  • ルートレイアウトは <html> タグと <body> タグを定義する必要があります
    • <title><meta> などの <head> タグを手動でルートレイアウトに追加するべきではありません。代わりに、Metadata API を使用してください。この API はストリーミングや <head> 要素の重複排除などの高度な要件を自動的に処理します。
  • ルートグループ を使用して複数のルートレイアウトを作成できます。
    • 複数のルートレイアウト間をナビゲートすると、フルページロードが発生します(クライアントサイドナビゲーションとは異なります)。例えば、app/(shop)/layout.js を使用する /cart から app/(marketing)/layout.js を使用する /blog にナビゲートすると、フルページロードが発生します。これは複数のルートレイアウトにのみ適用されます。

注意点

リクエストオブジェクト

レイアウトはナビゲーション時にクライアント側でキャッシュされ、不要なサーバーリクエストを回避します。

レイアウト は再レンダリングされません。ページ間をナビゲートする際の不要な計算を避けるためにキャッシュされ、再利用されます。レイアウトが生のリクエストにアクセスできないように制限することで、Next.js はレイアウト内でパフォーマンスに悪影響を与える可能性のある低速または高コストなユーザーコードの実行を防ぎます。

リクエストオブジェクトにアクセスするには、Server Components および Functions で headerscookies API を使用できます。

import { cookies } from 'next/headers'

export default async function Layout({ children }) {
  const cookieStore = await cookies()
  const theme = cookieStore.get('theme')
  return '...'
}
import { cookies } from 'next/headers'

export default async function Layout({ children }) {
  const cookieStore = await cookies()
  const theme = cookieStore.get('theme')
  return '...'
}

クエリパラメータ

レイアウトはナビゲーション時に再レンダリングされないため、古くなる可能性のある検索パラメータにアクセスできません。

更新されたクエリパラメータにアクセスするには、Page の searchParams プロップを使用するか、Client Component 内で useSearchParams フックを使用して読み取ることができます。Client Component はナビゲーション時に再レンダリングされるため、最新のクエリパラメータにアクセスできます。

'use client'

import { useSearchParams } from 'next/navigation'

export default function Search() {
  const searchParams = useSearchParams()

  const search = searchParams.get('search')

  return '...'
}
'use client'

import { useSearchParams } from 'next/navigation'

export default function Search() {
  const searchParams = useSearchParams()

  const search = searchParams.get('search')

  return '...'
}
import Search from '@/app/ui/search'

export default function Layout({ children }) {
  return (
    <>
      <Search />
      {children}
    </>
  )
}
import Search from '@/app/ui/search'

export default function Layout({ children }) {
  return (
    <>
      <Search />
      {children}
    </>
  )
}

パス名

レイアウトはナビゲーション時に再レンダリングされないため、古くなる可能性のあるパス名にアクセスできません。

現在のパス名にアクセスするには、Client Component 内で usePathname フックを使用して読み取ることができます。Client Component はナビゲーション時に再レンダリングされるため、最新のパス名にアクセスできます。

'use client'

import { usePathname } from 'next/navigation'

// 簡略化したパンくずリストロジック
export default function Breadcrumbs() {
  const pathname = usePathname()
  const segments = pathname.split('/')

  return (
    <nav>
      {segments.map((segment, index) => (
        <span key={index}>
          {' > '}
          {segment}
        </span>
      ))}
    </nav>
  )
}
'use client'

import { usePathname } from 'next/navigation'

// 簡略化したパンくずリストロジック
export default function Breadcrumbs() {
  const pathname = usePathname()
  const segments = pathname.split('/')

  return (
    <nav>
      {segments.map((segment, index) => (
        <span key={index}>
          {' > '}
          {segment}
        </span>
      ))}
    </nav>
  )
}
import { Breadcrumbs } from '@/app/ui/Breadcrumbs'

export default function Layout({ children }) {
  return (
    <>
      <Breadcrumbs />
      <main>{children}</main>
    </>
  )
}
import { Breadcrumbs } from '@/app/ui/Breadcrumbs'

export default function Layout({ children }) {
  return (
    <>
      <Breadcrumbs />
      <main>{children}</main>
    </>
  )
}

データの取得

レイアウトは children にデータを渡すことができません。ただし、同じデータをルートで複数回フェッチし、React の cache を使用してリクエストを重複排除することができます。これはパフォーマンスに影響を与えません。

あるいは、Next.js で fetch を使用すると、リクエストは自動的に重複排除されます。

export async function getUser(id: string) {
  const res = await fetch(`https://.../users/${id}`)
  return res.json()
}
import { getUser } from '@/app/lib/data'
import { UserName } from '@/app/ui/user-name'

export default async function Layout({ children }) {
  const user = await getUser('1')

  return (
    <>
      <nav>
        {/* ... */}
        <UserName user={user.name} />
      </nav>
      {children}
    </>
  )
}
import { getUser } from '@/app/lib/data'
import { UserName } from '@/app/ui/user-name'

export default async function Layout({ children }) {
  const user = await getUser('1')

  return (
    <>
      <nav>
        {/* ... */}
        <UserName user={user.name} />
      </nav>
      {children}
    </>
  )
}
import { getUser } from '@/app/lib/data'
import { UserName } from '@/app/ui/user-name'

export default async function Page() {
  const user = await getUser('1')

  return (
    <div>
      <h1>Welcome {user.name}</h1>
    </div>
  )
}
import { getUser } from '@/app/lib/data'
import { UserName } from '@/app/ui/user-name'

export default async function Page() {
  const user = await getUser('1')

  return (
    <div>
      <h1>Welcome {user.name}</h1>
    </div>
  )
}

子セグメントへのアクセス

レイアウトは自身より下のルートセグメントにアクセスできません。すべてのルートセグメントにアクセスするには、Client Component で useSelectedLayoutSegment または useSelectedLayoutSegments を使用できます。

'use client'

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

export default function NavLink({
  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>
  )
}
'use client'

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

export default function NavLinks({ slug, children }) {
  const segment = useSelectedLayoutSegment()
  const isActive = slug === segment

  return (
    <Link
      href={`/blog/${slug}`}
      style={{ fontWeight: isActive ? 'bold' : 'normal' }}
    >
      {children}
    </Link>
  )
}
import { NavLink } from './nav-link'
import getPosts from './get-posts'

export default async function Layout({
  children,
}: {
  children: React.ReactNode
}) {
  const featuredPosts = await getPosts()
  return (
    <div>
      {featuredPosts.map((post) => (
        <div key={post.id}>
          <NavLink slug={post.slug}>{post.title}</NavLink>
        </div>
      ))}
      <div>{children}</div>
    </div>
  )
}
import { NavLink } from './nav-link'
import getPosts from './get-posts'

export default async function Layout({ children }) {
  const featuredPosts = await getPosts()
  return (
    <div>
      {featuredPosts.map((post) => (
        <div key={post.id}>
          <NavLink slug={post.slug}>{post.title}</NavLink>
        </div>
      ))}
      <div>{children}</div>
    </div>
  )
}

メタデータ

metadata オブジェクト または generateMetadata 関数 を使用して、titlemeta などの <head> HTML 要素を変更できます。

import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'Next.js',
}

export default function Layout({ children }: { children: React.ReactNode }) {
  return '...'
}
export const metadata = {
  title: 'Next.js',
}

export default function Layout({ children }) {
  return '...'
}

知っておくと良いこと: <title><meta> などの <head> タグを手動でルートレイアウトに追加するべきではありません。代わりに、Metadata API を使用してください。この API はストリーミングや <head> 要素の重複排除などの高度な要件を自動的に処理します。

アクティブなナビゲーションリンク

usePathname フックを使用して、ナビゲーションリンクがアクティブかどうかを判断できます。

usePathname はクライアントフックであるため、ナビゲーションリンクを Client Component に抽出し、レイアウトにインポートする必要があります:

'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>
  )
}
'use client'

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

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

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

      <Link
        className={`link ${pathname === '/about' ? 'active' : ''}`}
        href="/about"
      >
        About
      </Link>
    </nav>
  )
}
import { NavLinks } from '@/app/ui/nav-links'

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <NavLinks />
        <main>{children}</main>
      </body>
    </html>
  )
}
import { NavLinks } from '@/app/ui/nav-links'

export default function Layout({ children }) {
  return (
    <html lang="en">
      <body>
        <NavLinks />
        <main>{children}</main>
      </body>
    </html>
  )
}

params に基づくコンテンツの表示

動的ルートセグメントを使用すると、params プロップに基づいて特定のコンテンツを表示または取得できます。

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

  return (
    <section>
      <header>
        <h1>{team}のダッシュボードへようこそ</h1>
      </header>
      <main>{children}</main>
    </section>
  )
}
export default async function DashboardLayout({ children, params }) {
  const { team } = await params

  return (
    <section>
      <header>
        <h1>{team}のダッシュボードへようこそ</h1>
      </header>
      <main>{children}</main>
    </section>
  )
}

クライアントコンポーネントでの params の読み取り

クライアントコンポーネント(async にできない)で params を使用するには、React の use 関数を使用してプロミスを読み取ることができます:

'use client'

import { use } from 'react'

export default function Page({
  params,
}: {
  params: Promise<{ slug: string }>
}) {
  const { slug } = use(params)
}
'use client'

import { use } from 'react'

export default function Page({ params }) {
  const { slug } = use(params)
}

バージョン履歴

バージョン変更内容
v15.0.0-RCparams がプロミスになりました。コードモッドが利用可能です。
v13.0.0layout が導入されました。