今までの Next.js ではpagesディレクトリのファイル・ディレクトリがそのまま URL に反映、例えばpage/sample.tsxというファイルを作成した場合は、/sampleにアクセスすればそのページを見ることができました。
一方 Next13 からのappディレクトリ構成では、そのルーティング方法が変わっていますので、どのように変更があったか見ていきましょう。
※ 前回の記事を見てもらうことを推奨します
URL パスの決まり方
URL パス (https://tyotto-good.com/nextjs) のようなドメインに続くパスは、appディレクトリの配下にあるディレクトリによって決まります。
例えば、以下のようなディレクトリ構成を考えます。
app
├── page.tsx
├── layout.tsx
└── shops
└── page.tsx
この時のapp/page.tsxは URL / の UI に相当し(ルートパス)、app/shops/page.tsxは URL /shopsの UI に相当します。
動的なパスの対応
パスが動的に変更する場合、例えば
[https://tyotto-good.com/shops/xxx](https://tyotto-good.com/shops/xxx) の xxx が定まっていない場合などに対応するには、[]を用いる必要があります。
以下のようなディレクトリ構成にした場合は、[id]が動的パスにあたり、[/shops/](https://tyotto-good.com/shops/xxx)foodsや、/shops/fishなど id 箇所が動的な値を入れることができるようになります。
app
└── shops
├── [id]
│ └── page.tsx
動的なパスを用いた場合は、page ファイルの props でparamsというパラメータを受け取ることができ、[/shops/](https://tyotto-good.com/shops/xxx)foodsにアクセスあればshops/[id]/page.tsxのparamsは、
{ id: 'foods' }
のようなオブジェクトになります。詳細は、page ファイルについてで述べます。
後続の全てのパスに対応する
さらに後続のパスにまで対応したい場合は、[…folderName] と…を追加することで後続のパス全てに対応することができます。
以下のようなディレクトリ構成を想定します。
app
└── shops
├── [...id]
│ └── page.tsx
この場合、/shops/foods/rice/1にアクセスすると、shops 以下全てのパスに対応することができるので、/shops/page.tsxが処理を引き受けるようになります。
また、この場合のparamsは以下のように値を配列で受け取るようになります。
{
id: ["food", "rice", "1"];
}
注意点としては、こちらの記法を使った場合は/shopsパスに対応しないという点です。
アクセスした場合は、404 判定となります。
こちらに対応したい場合は、[[…folderName]] を用います。括弧を 2 重にすると一致する範囲が広がり、/shopsにアクセスした時ページが表示されるようになります。
app
└── shops
├── [[...id]]
│ └── page.tsx
app ディレクトリ以下のディレクトリを URL パスに直接対応させたくない場合
URL のパスとは紐付けづに、app ディレクトリ以下のディレクトリをグルーピングしたい場合があると思います。
そのような場合は、ルートグループを用いて、ディレクトリ名を()囲うことで対応できます。
例えば、以下のような構成を考えます。
app
├── (category)
│ └── item
│ └── page.tsx
この場合、(category)がルートグループになっており、URL に影響を与えることなくグルーピングしていることになります。
よって、/app/itemにアクセスすると、/app/(category)/item/page.tsx の UI が表示されることになります。
ただこの場合、
app
├── (category)
│ └── item
│ └── page.tsx
├── (sample)
│ └── item
│ └── page.tsx
のような構成にしてしまうとパスがバッティング(/app/itemパスが複数存在)してしまいエラーを引き起こすので注意が必要です。
page ファイルについて
この時のpage.tsxはパスに紐づく UI を表し、今までのpagesディレクトリ配下のファイルのような役割を果たします。
受け取る props は先ほどのセクションで説明があったように、以下があります。
| props 名 | 説明 |
|---|---|
| params (optional) | 動的なパスパラメータ |
| searchParams (optional) | クエリパラメータ |
どちらも URL に紐づくパラメータを受け取ることがわかります。
では以下のようなディレクトリ構成・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"}