🪺

【App Router】 LayoutsとTemplatesの基礎と使い分け

2024/08/25に公開

はじめに

こんにちは!
株式会社BLUEISH エンジニアの佐々木(@osasasasa22)です。
普段はNext.jsを用いたフロントエンド開発に携わっています。

今回は、App Routerの機能であるApp RouterのLayoutsとTemplatesについてご紹介します。
これらの機能は、ページや共有レイアウト、テンプレートを簡単に作成できる新しいファイル規約を導入しており、モジュール化されたUIの作成や状態管理を直感的に行うことができます。

それでは、詳しく見ていきましょう!

Layouts

https://nextjs.org/docs/app/building-your-application/routing/layouts-and-templates

Layoutsとは

Next.js App Routerには、複数のページで共有するUI(例えば、ヘッダー、フッター、サイドバーなど)を簡単に作成できるレイアウト機能があります。
Layoutsはlayout.tsxファイルを作成し、その中で定義します。

Layoutsの主な特徴

  • layout.tsx(または.js)ファイルを作成して定義します。
  • Root Layoutと呼ばれる、最上位のLayoutsが存在します。
    これはすべてのページで共有される基本的なLayoutsです。
  • URLの各セグメント(部分)ごとに、独自のRoot Layoutを定義することができます。
    これにより、サイトの異なる部分で異なるレイアウトを使用できます。
  • Root Layoutは入れ子にすることができます。
    つまり、親Layoutsの中に子レイアウトを配置することが可能です。
  • 通常、Layoutsはサーバーコンポーネントとして機能しますが、必要に応じてクライアントコンポーネントとしても実装できます。
  • Layoutsト内でもデータフェッチが可能です。これにより、Layoutsに動的なデータを組み込むことができます。

中でも特に重要なのがRoot Layoutです。

Root Layout

Root Layoutはappディレクトリの最上位で定義され、アプリ全体に適用されます。

実際のプロジェクトですと、以下のように定義しています。

app/layout.tsx
import { GoogleAnalytics } from '@next/third-parties/google';
import { Metadata } from 'next';
import { Noto_Sans_JP, Inter } from 'next/font/google';
import '@/styles/globals.css';

// 1. フォントの設定
const noto = Noto_Sans_JP({
  weight: ['400', '700'],
  variable: '--font-noto-sans-jp',
  subsets: ['latin'],
});

const inter = Inter({
  weight: ['400', '700'],
  variable: '--font-inter',
  subsets: ['latin'],
});

type Props = {
  children: React.ReactNode;
};

export default function RootLayout({ children }: Props) {
  return (
    <html lang="ja" className={`${noto.variable} ${inter.variable}`}>
      <head>
        {/* 2. ファビコンの設定 */}
        <link rel="icon" href="/favicon.svg" type="image/svg+xml" />
      </head>
      <body>
        {/* ページコンテンツ */}
        {children}
      </body>
      {/* 3. Google Analytics */}
      {process.env.NEXT_PUBLIC_GA_TAG && (
        <GoogleAnalytics gaId={process.env.NEXT_PUBLIC_GA_TAG} />
      )}
    </html>
  );
}

// 4. メタデータの設定
export const metadata: Metadata = {
  title: 'アプリ名',
  description: 'アプリの説明が入ります',
  openGraph: {
    images: [
      {
        url: 'https://example.com/og-image.jpg',
      },
    ],
  },
};

このように、Root Layoutでは、基本的なHTML構造の定義のほか、

  1. フォントの設定
  2. ファビコンの設定
  3. Google Analyticsの統合
  4. メタデータ(サイトのタイトル、説明、OGP画像など)の定義

など、全ページで共通して適応させたい設定や定義を行っていることがわかるかと思います。

Nested Layouts

特定のルートセグメント (フォルダー) 内にレイアウトを追加することで、レイアウトをネストできます。

以下はネストされたアプリ構造の例です。

app/
│
├── layout.tsx        # ルートレイアウト
├── page.tsx          # ホームページ
│
├── (auth)/
│   ├── layout.tsx    # 認証ページ共通レイアウト
│   ├── login/
│   │   └── page.tsx  # ログインページ
│   └── signup/
│       └── page.tsx  # サインアップページ
│
└── mypage/
    ├── layout.tsx    # マイページ共通レイアウト
    ├── page.tsx      # マイページトップ
    │
    ├── profile/
    │   └── page.tsx  # プロフィールページ
    │
    └── settings/
        └── page.tsx  # アカウント設定ページ
    

この構造では、app/layout.tsxはすべてのページに適用されるRoot Layoutとなります。
また、(auth)/layout.tsxは、認証関連のページにのみ、mypage/layout.tsxはマイページ関連のページのみに適用される追加のLayoutsとなります。

ネストされたLayoutsの例

app/mypage/layout.tsx (マイページ用レイアウト)
export default function MypageLayout(props: Props) {
  return (
    <div className={container}>
      <div className={sidebar}>
        <UserProfileSummary user={user} />
        <SidebarNavigation />
      </div>
      <div className={content}>
        {children}
      </div>
    </div>
  );
}

(Pages Routerではどうしていたか?)

ちなみに、従来のPages Routerでも共有UIを作成することは可能でしたが、それほど直感的ではありませんでした。
通常ですと、以下のような方法が用いられていました。

  1. カスタム_app.jsファイルでグローバルレイアウトを定義
  2. レイアウトコンポーネントでページコンポーネントをラップして、特定のページに共通のレイアウトを適用
  3. 各ページコンポーネント内で共通のレイアウトコンポーネントをインポートして使用

App Routerの Nesting Layoutsは、こうした方法よりもはるかに直感的で扱いやすいものになりました💡

Templates

https://nextjs.org/docs/app/building-your-application/routing/layouts-and-templates

Templatesとは

Layoutsと同様に、特定のルートにのみ適用されるUIを共有したい場合、Templatesを使用することもできます。
Layoutsがルート間で状態を保持するのに対し、Templatesは新しいルートに移動するたびに新しいインスタンスを作成するので、ページごとに状態をリセットしたり、特定のページに固有の挙動を追加したりすることができます。

ページ遷移時にuseEffectを再同期化したい場合や、ナビゲーション時にClient Componentの状態をリセットしたい場合などに使えますね。

app/template.tsx
export default function Template({ children }: TemplateProps) {
  return <div className="template">{children}</div>;
}

ちなみに、従来のPages Routerには、Templatesに直接対応する機能がなく、ページコンポーネント内で手動で状態のリセットや効果の再同期を行う必要がありました。。!

App Routerではこういった処理をテンプレートが自動で行ってくれるので、開発者はページの構造や動作を定義するだけで済むようになり、ページの全体的な設計に集中できるようになりました。

おわりに

今回は、App RouterのLayoutsとTemplatesについてご紹介させていただきました。
この2つを適切に活用することで、モジュール化されたUIの作成や状態管理を直感的に実現することができます。

その他のApp Routerの機能について知りたい方は、ぜひ他の記事もご覧ください👀
https://zenn.dev/blueish/articles/4b2ae3781ade57
https://zenn.dev/blueish/articles/61526c0983362e#discuss

最後までお読みいただきありがとうございました。

Discussion