Sponsor
ChatHubChatHub Use GPT-4, Gemini, Claude 3.5 and more chatbots side-by-side
ここをクリック
Menu

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

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

レイアウト

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

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

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

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

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

ルートレイアウトはappディレクトリのトップレベルで定義され、すべてのルートに適用されます。このレイアウトは必須であり、htmlタグとbodyタグを含める必要があります。これによりサーバーから返される初期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ファイルを追加します:

Nested Layout
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つのレイアウトは次のようにネストされます:

Nested Layouts

補足

  • レイアウトには.js.jsx、または.tsxファイル拡張子を使用できます。
  • ルートレイアウトのみが<html><body>タグを含めることができます。
  • 同じフォルダにlayout.jspage.jsファイルが定義されている場合、レイアウトはページをラップします。
  • レイアウトはデフォルトでサーバーコンポーネントですが、クライアントコンポーネントに設定することもできます。
  • レイアウトはデータをフェッチできます。詳細についてはデータフェッチングセクションをご覧ください。
  • 親レイアウトとその子の間でデータを渡すことはできません。ただし、同じルートで複数回データをフェッチすることができ、Reactはリクエストを自動的に重複排除するためパフォーマンスには影響しません。
  • レイアウトはpathnameにアクセスできません(詳細はこちら)。ただし、インポートされたクライアントコンポーネントはusePathnameフックを使用してpathnameにアクセスできます。
  • レイアウトは自分より下のルートセグメントにアクセスできません。すべてのルートセグメントにアクセスするには、クライアントコンポーネントでuseSelectedLayoutSegmentまたはuseSelectedLayoutSegmentsを使用できます。
  • ルートグループを使用して、特定のルートセグメントを共有レイアウトに含めたり除外したりできます。
  • ルートグループを使用して、複数のルートレイアウトを作成できます。例はこちらをご覧ください。
  • pagesディレクトリからの移行: ルートレイアウトは_app.js_document.jsファイルを置き換えます。移行ガイドをご覧ください。

テンプレート

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

そのような特定の動作が必要な場合、テンプレートはレイアウトよりも適切なオプションになるかもしれません。例えば:

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

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

template.js special file
app/template.tsx
TypeScript
export default function Template({ children }: { children: React.ReactNode }) {
  return <div>{children}</div>
}

ネストの観点では、template.jsはレイアウトとその子の間でレンダリングされます。以下は簡略化した出力例です:

Output
<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>要素の重複排除などの高度な要件を自動的に処理するメタデータ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>
  )
}