レイアウトとテンプレート
特殊ファイルlayout.jsとtemplate.jsを使用すると、複数のルート間で共有されるUIを作成できます。このページでは、これらの特殊ファイルの使用方法とタイミングについて説明します。
レイアウト
レイアウトは複数のルート間で共有されるUIです。ナビゲーション時、レイアウトは状態を保持し、インタラクティブな状態を維持し、再レンダリングされません。レイアウトはネストすることもできます。
layout.js
ファイルからReactコンポーネントをデフォルトエクスポートすることで、レイアウトを定義できます。コンポーネントはchildren
プロップを受け取る必要があり、これはレンダリング中に子レイアウト(存在する場合)またはページで構成されます。
例えば、以下のレイアウトは/dashboard
と/dashboard/settings
ページで共有されます:
export default function DashboardLayout({
children, // ページまたはネストされたレイアウトになります
}: {
children: React.ReactNode
}) {
return (
<section>
{/* 共有UIをここに含めます(ヘッダーやサイドバーなど) */}
<nav></nav>
{children}
</section>
)
}
ルートレイアウト(必須)
ルートレイアウトはapp
ディレクトリのトップレベルで定義され、すべてのルートに適用されます。このレイアウトは必須であり、html
タグとbody
タグを含める必要があります。これによりサーバーから返される初期HTMLを変更できます。
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
{/* レイアウトUI */}
<main>{children}</main>
</body>
</html>
)
}
レイアウトのネスト
デフォルトでは、フォルダ階層内のレイアウトはネストされており、children
プロップを通じて子レイアウトをラップします。特定のルートセグメント(フォルダ)内にlayout.js
を追加することで、レイアウトをネストできます。
例えば、/dashboard
ルート用のレイアウトを作成するには、dashboard
フォルダ内に新しいlayout.js
ファイルを追加します:
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return <section>{children}</section>
}
上記の2つのレイアウトを組み合わせると、ルートレイアウト(app/layout.js
)がダッシュボードレイアウト(app/dashboard/layout.js
)をラップし、それがapp/dashboard/*
内のルートセグメントをラップします。
2つのレイアウトは次のようにネストされます:
補足:
- レイアウトには
.js
、.jsx
、または.tsx
ファイル拡張子を使用できます。- ルートレイアウトのみが
<html>
と<body>
タグを含めることができます。- 同じフォルダに
layout.js
とpage.js
ファイルが定義されている場合、レイアウトはページをラップします。- レイアウトはデフォルトでサーバーコンポーネントですが、クライアントコンポーネントに設定することもできます。
- レイアウトはデータをフェッチできます。詳細についてはデータフェッチングセクションをご覧ください。
- 親レイアウトとその子の間でデータを渡すことはできません。ただし、同じルートで複数回データをフェッチすることができ、Reactはリクエストを自動的に重複排除するためパフォーマンスには影響しません。
- レイアウトは
pathname
にアクセスできません(詳細はこちら)。ただし、インポートされたクライアントコンポーネントはusePathname
フックを使用してpathnameにアクセスできます。- レイアウトは自分より下のルートセグメントにアクセスできません。すべてのルートセグメントにアクセスするには、クライアントコンポーネントで
useSelectedLayoutSegment
またはuseSelectedLayoutSegments
を使用できます。- ルートグループを使用して、特定のルートセグメントを共有レイアウトに含めたり除外したりできます。
- ルートグループを使用して、複数のルートレイアウトを作成できます。例はこちらをご覧ください。
pages
ディレクトリからの移行: ルートレイアウトは_app.js
と_document.js
ファイルを置き換えます。移行ガイドをご覧ください。
テンプレート
テンプレートはレイアウトと同様に子レイアウトやページをラップしますが、レイアウトがルート間で保持され状態を維持するのに対し、テンプレートはナビゲーション時に子ごとに新しいインスタンスを作成します。つまり、ユーザーがテンプレートを共有するルート間を移動すると、子の新しいインスタンスがマウントされ、DOM要素が再作成され、クライアントコンポーネントの状態は保持されず、エフェクトが再同期されます。
そのような特定の動作が必要な場合、テンプレートはレイアウトよりも適切なオプションになるかもしれません。例えば:
- ナビゲーション時に
useEffect
を再同期する場合。 - ナビゲーション時に子クライアントコンポーネントの状態をリセットする場合。
テンプレートはtemplate.js
ファイルからデフォルトのReactコンポーネントをエクスポートすることで定義できます。コンポーネントはchildren
プロップを受け取る必要があります。
export default function Template({ children }: { children: React.ReactNode }) {
return <div>{children}</div>
}
ネストの観点では、template.js
はレイアウトとその子の間でレンダリングされます。以下は簡略化した出力例です:
<Layout>
{/* テンプレートには一意のキーが与えられます */}
<Template key={routeParam}>{children}</Template>
</Layout>
例
メタデータ
メタデータAPIを使用して、title
やmeta
などの<head>
HTML要素を変更できます。
メタデータはlayout.js
またはpage.js
ファイルでmetadata
オブジェクトまたはgenerateMetadata
関数をエクスポートすることで定義できます。
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Next.js',
}
export default function Page() {
return '...'
}
補足: ルートレイアウトに
<title>
や<meta>
などの<head>
タグを手動で追加するべきではありません。代わりに、ストリーミングや<head>
要素の重複排除などの高度な要件を自動的に処理するメタデータAPIを使用してください。
利用可能なメタデータオプションの詳細については、APIリファレンスをご覧ください。
アクティブなナビゲーションリンク
usePathname()フックを使用して、ナビゲーションリンクがアクティブかどうかを判断できます。
usePathname()
はクライアントフックであるため、ナビゲーションリンクをクライアントコンポーネントに抽出し、それをレイアウトやテンプレートにインポートする必要があります:
'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>
)
}
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>
)
}