Menu

レイアウトとテンプレート

特殊なファイル layout.jstemplate.js により、ルート間で共有されるUIを作成できます。このページでは、これらの特殊なファイルの使用方法と使用タイミングについて説明します。

レイアウト

レイアウトは、複数のルート間で共有される UIです。ナビゲーション時に、レイアウトは状態を保持し、インタラクティブで、再レンダリングされません。レイアウトはネストすることもできます。

layout.jsファイルからデフォルトエクスポートされるReactコンポーネントを定義することでレイアウトを作成できます。このコンポーネントは、レンダリング中に子レイアウト(存在する場合)またはページで埋められるchildrenプロップを受け取る必要があります。

例えば、以下のレイアウトは /dashboard/dashboard/settings ページで共有されます:

layout.js特殊ファイル
app/dashboard/layout.tsx
TypeScript
export default function DashboardLayout({
  children, // ページまたはネストされたレイアウトになります
}: {
  children: React.ReactNode
}) {
  return (
    <section>
      {/* ここにヘッダーやサイドバーなどの共有UIを含めます */}
      <nav></nav>
 
      {children}
    </section>
  )
}

ルートレイアウト(必須)

ルートレイアウトはappディレクトリのトップレベルで定義され、すべてのルートに適用されます。このレイアウトは必須で、htmlbodyタグを含める必要があり、サーバーから返される初期HTMLを変更できます。

app/layout.tsx
TypeScript
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ファイルを追加します:

ネストされたレイアウト
app/dashboard/layout.tsx
TypeScript
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.jspage.js ファイルが定義されている場合、レイアウトはページをラップします。
  • レイアウトはデフォルトでサーバーコンポーネントですが、クライアントコンポーネントに設定できます。
  • レイアウトはデータを取得できます。詳細はデータフェッチセクションを参照してください。
  • 親レイアウトとその子間でデータを渡すことはできません。ただし、同じルートでデータを複数回フェッチでき、Reactは自動的にリクエストを重複排除し、パフォーマンスに影響を与えません。
  • レイアウトは pathname にアクセスできません(詳細はこちら)。ただし、インポートされたクライアントコンポーネントは usePathname フックを使用してパス名にアクセスできます。
  • レイアウトは自身の下のルートセグメントにアクセスできません。すべてのルートセグメントにアクセスするには、クライアントコンポーネントで useSelectedLayoutSegment または useSelectedLayoutSegments を使用できます。
  • ルートグループを使用して、特定のルートセグメントを共有レイアウトに対してオプトインまたはオプトアウトできます。
  • ルートグループを使用して、複数のルートレイアウトを作成できます。例はここを参照してください。
  • pagesディレクトリからの移行: ルートレイアウトは _app.js_document.js ファイルに取って代わります。移行ガイドはこちら

テンプレート

テンプレートは、子レイアウトまたはページをラップするという点でレイアウトと似ています。レイアウトがルート間で保持され状態を維持するのに対し、テンプレートはナビゲーション時に子ごとに新しいインスタンスを作成します。つまり、ユーザーがテンプレートを共有するルート間を移動する場合、子の新しいインスタンスがマウントされ、DOMエレメントが再作成され、クライアントコンポーネントの状態は保持されず、エフェクトが再同期されます。

これらの特定の動作が必要な場合があり、テンプレートはレイアウトよりも適切な選択肢となります。例えば:

  • ナビゲーション時に useEffect を再同期する場合
  • ナビゲーション時にクライアントコンポーネントの子の状態をリセットする場合

テンプレートは、template.jsファイルからデフォルトでReactコンポーネントをエクスポートすることで定義できます。このコンポーネントはchildrenプロップを受け取る必要があります。

template.js特殊ファイル
app/template.tsx
TypeScript
export default function Template({ children }: { children: React.ReactNode }) {
  return <div>{children}</div>
}

ネスト構造について、template.jsはレイアウトと子の間でレンダリングされます。簡略化された出力は次のとおりです:

出力
<Layout>
  {/* テンプレートには一意のキーが与えられることに注意 */}
  <Template key={routeParam}>{children}</Template>
</Layout>

メタデータ

メタデータAPIを使用して、titlemetaなどの<head> HTMLエレメントを変更できます。

メタデータは、layout.jsまたはpage.jsファイルでmetadataオブジェクトまたはgenerateMetadata関数をエクスポートすることで定義できます。

app/page.tsx
TypeScript
import type { Metadata } from 'next'
 
export const metadata: Metadata = {
  title: 'Next.js',
}
 
export default function Page() {
  return '...'
}

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

API リファレンスで利用可能なメタデータオプションの詳細を確認できます。

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

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

usePathname()はクライアントサイドのフックであるため、ナビゲーションリンクをクライアントコンポーネントに抽出する必要があります。これはレイアウトやテンプレートにインポートできます:

app/ui/nav-links.tsx
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>
  )
}
app/layout.tsx
TypeScript
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>
  )
}