Next13 の app ディレクトリ配下では、以下の特殊ファイルを定義することで、構成をより簡潔にし、ボイラープレートを減らすようにしています。
page.js
layout.js
template.js
loading.js
head.js
error.js
not-found.js
route.js
かなり数が多いので、今回は主に UI に関わる特殊ファイルについて説明させていただきます。
※ .js
、.jsx
、または.tsx
ファイル拡張子を使用できます。
page.js(tsx)
Next13 の URL パスの決まり方と重複しますが、page.tsx
はパスに紐づく UI を表し、default export したモジュールがページ UI となります。
props(page.js)
受け取る props は以下です。
props 名 | 説明 | 例 |
---|---|---|
params (optional) | 動的なフォルダ変数と動的なパスパラメータのオブジェクト | {"id":["food"]} |
searchParams (optional) | クエリパラメータと値のオブジェクト | {"q":"rice"} |
どちらも URL に紐づくパラメータを受け取ることがわかります。
例(page.js)
では以下のようなディレクトリ構成・page ファイルの場合の挙動を確認してみましょう。
app
└── shops
├── [...id]
│ └── page.tsx
page.tsx
import { NextPage } from "next";
interface PageProps {
params: {
id: string[];
};
searchParams?: { [key: string]: string | string[] | undefined };
}
const ShopPage: NextPage<PageProps> = ({ params, searchParams }) => {
return (
<main>
<div>params: {JSON.stringify(params)}</div>
<div>searchParams: {JSON.stringify(searchParams)}</div>
</main>
);
};
export default ShopPage;
この場合、/shops/food?q=rice
にアクセスがあった場合、以下のような表示になります。
params: {"id":["food"]}
searchParams: {"q":"rice"}
layout.js(tsx)
layout.tsx
は、定義されたディレクトリ以下のページ間で共有される UI です。layout
が共有されているページ間を遷移した場合、再レンダリングは発生しません。
props(layout.js)
以下の props を受け取ります。layout.tsx
はページ UI をラップするので、必ずchildren
を受け取り、ラップして返す必要があります。
props 名 | 説明 | 例 |
---|---|---|
children | layout にラップされているページ UI(page.js, loading.js などが該当) | |
params (optional) | 動的なフォルダ変数と動的なパスパラメータのオブジェクト | 動的なフォルダ変数と動的なパスパラメータのオブジェクト |
例(layout.js)
では実際のlayout.tsx
の例を見てみます。以下の構成で、
app
└── shops
├── [id]
│ ├── layout.tsx
│ └── page.tsx
└────── page.tsx
layout.tsx
を以下のように定義します。
const ShopLayout = ({ children }: { children: React.ReactNode }) => {
return (
<div>
<h1>Shop</h1>
{children}
</div>
);
};
export default ShopLayout;
この状態で、/shops/food
にアクセスすると、layout の状態が引き継がれ、Shop という文字が表示されるはずです、/shop/hoge
にしても同様です。
しかし、この layout から外れている/shop にアクセスした場合は、layout の状態は引き継がれず、あくまでlayout.tsx
と同階層以下の時に適用されることがわかります。
layout のユースケース
layout
には、ルートレイアウト、ページレイアウトと 2 種類の使い方があり、ページレイアウトは先ほど例に挙げたものになります。
一方でルートレイアウトとは、app ディレクトリ直下に置かれるlayout.tsx
のことであり、アプリケーション全体のレイアウトを意味します。
app
├── layout.tsx
├── page.tsx
ルートレイアウト
ルートレイアウトは、今までの_document.tsx
、_app.tsx
に代わるものであり、サーバーから返される最初の HTML を変更することができます。
app/layout.tsx
import "./globals.css";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<head />
<body>{children}</body>
</html>
);
}
上の例のように、グローバルの CSS を読み込んだり、全体の HTML を定義しています。
ルートレイアウトに関しては、以下のような点に注意する必要があります。
- ルートレイアウトは必須です
- Next.js はタグを自動生成しないので、ここで
<html>
,<body>
タグを定義する必要があります。 - また、ルートレイアウトは Server Component になっており、Client Component に変更することができません。
template.js(tsx)
template.tsx
はlayout.tsx
と類似しており、ページをラップするのですが、違いは状態を共有しているページ間を移動した時に、子要素のインスタンスを作成し、再レンダリングされるという点です。
例えば、template 内で、アニメーションを行いたい場合や、useEffect
やuseState
などを用いてページ遷移ごとにログを取得する時などに必要になります。
パフォーマンスの観点からは、layout に劣るため、できれば layout を用いて、それで実現できない場合に利用するのがよいでしょう。
また、layout.tsx と同階層に置いた場合は、layout にラップされます(template が子要素になる)。
例(template.js)
以下のような構成とします。
app
└── shops
├── [id]
│ ├── layout.tsx
│ ├── page.tsx
│ └── template.tsx
template.tsx
, layout.tsx
を以下のように定義します。
template.tsx
const ShopLayout = ({ children }: { children: React.ReactNode }) => {
return (
<div>
<h1>Shop Layout</h1>
{children}
</div>
);
};
export default ShopLayout;
layout.tsx
const ShopLayout = ({ children }: { children: React.ReactNode }) => {
return (
<div>
<h1>Shop Layout</h1>
{children}
</div>
);
};
export default ShopLayout;
この場合は、出力は以下のようになり、layout が template をラップしていることがわかるかと思います。
<div>
<h1>Shop Layout</h1>
<div>
<h2>Shop Template</h2>
<main>
<!-- children -->
</main>
</div>
</div>