📡

nextjsの[[...props]]によるOptional catch all routesの使い道・利用例

2 min read

Optional catch all routes

next.jsのDynamic Routingとして、Optional catch all routesというのがある。、

これはパスとしてで[[...props]]と記述するものになる。

つまりDynamic Routingは[props], [...props], [[...props]]と三種類の記述がある。
かなり似ていてややこしいが、下記のようにまとめられる。

/foo /foo/baz /foo/baz/bar
/foo/[props].jsx ⭕️
/foo/[...props].jsx ⭕️ ⭕️
/foo/[[...props]].jsx ⭕️ ⭕️ ⭕️

[[...props]]はあまり利用例が多くはないが、例えば疑似ルーティングのような事が出来る。

Optional catch all routesの利用例

next.jsにおいて複数のページでのgetServerSideProps/getStaticPropsやレイアウト等を共通化したい場合、通常はgetServerSideProps/getStaticPropsの処理を別途切り出して呼び出したり、レイアウトをコンポーネント化して切り出すのが一般的な手法となる。

この別解として、ルーティングで [[...props]]と記載するを利用した手法でこれを解決が出来る。

例えばわかりやすい例として、「同じデータを別な形式で表示をさせたい」のようなケースは想像しやすいだろう。

今回は「リスト表示」「タイル表示」「タイル表示(説明文付き)」のような切り替えを出来るものを想定してみる。

まずファイルとして/dogs/[[path]].tsxのようなファイルを作る。

getServerSidePropsはこのような感じになる。

/** /dogs/[[path]].tsx */

export const getServerSideProps: GetServerSideProps = async (req) => {
  const query = req.query
  const dogs = await getDogs() // 共通で利用するデータ取得

  return {
    props: {
      items: dogs,
      // string[]型に統一する。query.paths.join("/")してもいいかも
      paths: query.paths ?? [] 
    },
  }
}

ページの受け口はこんな具合になる。ここの実装はなんでもよが、概ね疑似ルータっぽくなることがわかる。

const ViewRouter: FC<{ items: Item[], paths: string[] }> = ({ items, paths }) => {
  const path = paths.join("/")
  switch (path) {
    case "tile":
      return <TileView items={items} />
    case "tile/description":
      return <TileView items={items} withDescription />
    case "list":
    default:
      return <ListView items={items} />
  }
}

const Page = (props) => {
  return <Layout>
    <ViewRouter {...props} />
  </Layout>
}

TileViewListViewは単にコンポーネントの組み合わせなので割愛する。
気になる場合はソースより確認いただきたい

デモ

画像データにはdog.ceoを利用させてもらった

preview