💭

【Next.js】Intercepting Routesって何やねん

2024/07/26に公開

はじめに

本記事では、Next.jsのIntercepting Routesの基本についてまとめます。
Parallel Routesと合わせて用いられることが多く、便利な機能なのですが、意外と基本的なことを知っていないと使いこなせないかつ意図した挙動にならないということでハマりやすかったりするので、まとめておこうと思います

https://nextjs.org/docs/app/building-your-application/routing/intercepting-routes

https://nextjs.org/docs/app/building-your-application/routing/parallel-routes

概要

Intercepting RoutesとはRouteを横取り(インターセプト)するためのRoute定義です。
定義されているRoute(Segment①)をインターセプトし、Intercepting Routeで定義したRoute(Segment②)が異なるRoute(Segment①)をインターセプトすることで異なるフォルダに定義されてRoute(Segment①)を画面として定義してくれる機能です。

使い方

相対パスのように使います。
フォルダ名の先頭に(..)をつけてその後にインターセプトしたいRouteのフォルダ名を記述するように定義します(例:(..)photo

ドキュメントにあるように以下の規則に則っています
これは、import文の相対パスによるimportをイメージするとわかりやすいかもしれません

  • (.) 同じ階層
  • (..) 1つ上の階層
  • (..)(..) 2つ上の階層
  • (...) app ディレクトリのroot

実装例題

それでは、例題を見ながら一緒に考えてみましょう

例題

  • 以下のRoute定義があった時、feedphoto/[photoId]のSegmentをインターセプトしたい場合、どのようにIntercepting Routeを定義したらよいか?

ヒント: feedの1つ下にIntercepting Routeを作りたいので、feedの1つ下の階層から見るとphotoは相対パスでどこに位置するか?

app
 ├── photo
 │    └── [photoId]
 │         └── page.tsx
 │
 └── feed

例題の回答

回答ですが、以下のようになります
正解できましたか?

解説ですが、ヒントに記載のあるようにfeedの1つの下の階層からはphotoは1つ上の階層にあたります
そのため、photo/[photoId]をインターセプトしたい場合(..)を使用して、続けてphoto/[photoId]と同じ階層を作って上げるとIntercepting Routeを作ることができます。

app
 ├── photo
 │    └── [photoId]
 │         └── page.tsx
 │
 └── feed
      └── (..)photo
           └── [photoId]
                └── page.tsx

同じ階層で作りたいなら、以下のようになります。

app
 ├── photo
 │    └── [photoId]
 │         └── page.tsx
 │
 └── (.)photo
      └── [photoId]
           └── page.tsx

基本の使い方は以上です。

注意点

Intercepting Routeを使うにあたって、1つ注意点があります。
それは「インターセプトが発生するのはソフトナビゲーションのときのみ」というルールがあることです。
Next.jsでは基本的にアプリケーション全体でソフトナビゲーションなので、あまり気にする必要はないですが、ページに共通部品を使わない固有のページなどを使っていると発生しないので注意が必要です。

  • ソフトナビゲーション: 画面の再読み込みが発生しない
  • ハードナビゲーション: 画面の再読み込みが発生する

Next.jsはいわゆるSPAナビゲーションで画面で変更のあった箇所のみ読み込みます
このように一部のみ画面の読み込みが発生するのでソフトナビゲーションの呼ばれます
ハードナビゲーションは画面全体が読み込みされます

応用

最後に、応用としてParallel Routeと合わせて使うケースを見ていきましょう。
公式ドキュメントにも例があったりしますが、これは、モーダルや、プレビュー画面で一覧から選択したものを画面の左右どちらかに表示するなどと行ったケースで使用できます。

また、Parallel Routeと組み合わせることによるメリットもかなりあります。
モーダルのケースを考えてみましょう。

メリット

本来、モーダルの実装では「一覧のある画像をクリック→詳細な画像」というようなものだったり、「新規登録ボタン→新規登録フォームのモーダル」が開くなどが一般的です。
ここまでは同じように見えます。ただ、Next.jsの機能を使うと「モーダルのURLも保持」することができます。

つまり、詳細なら詳細画面へのURLをモーダルが持っているので、リンクとしてコピーすることができたり、それを共有することもできます。
また、モーダルが開いたときにブラウザバックすると一覧に戻れたり、「詳細モーダルを開く→次へボタン押す→次の詳細モーダル」というケースではブラウザバックで一覧ではなく1つ前に開いていた詳細モーダルに戻ることも可能です。

このようなことが実現できるのはURLだけ変更しているけれども、Next.jsが裏側で遷移元の画面を「裏側」に残したまま別のURLのページを開くことができているためです。
まとめると以下のようになります。

  • モーダル表示でも遷移先のURLを保持できる
  • 遷移元の画面に戻らずに次々にモーダル上で画面遷移することもできる
  • モーダル(遷移先)のURLをshare・コピーできる

実装例

モーダルを実装する例ですが、以下のようにRoute定義します。
今回の場合、Parallel Routeを使用するので、通常のIntercepting Routeの相対パスの考え方をそのまま流用すると上手くいきません。

解説すると、今回は@modalの1つ下に作るので、そこからphotoを見ると(..)(..)photoなどとやればよいと思う人もいるのですが、Parallel Routeを用いる場合、基準がParallel Routeに変わります。
つまり、Parallel Routeから見てインターセプトしたいRouteはどこに位置するのかを考える必要があります。

そのため、以下のような定義になります。
この部分でつまづき妥協する人が多いように思いますし、私も一度妥協したり、理解せずにコピペを流用するなどしました、、

app
 ├── photo
 │    └── [photoId]
 │         └── page.tsx
 │
 └── photos
      ├── @modal
      │    ├── (..)photo
      │    │     └── [photoId]
      │    │          └── page.tsx
      │    └── default.tsx 
      └── layout.tsx (ここでmodalをslotで受け取る)

上記のようにすることで/photo/1などのルートをインターセプトして一覧からモーダル表示することができます。
今回はルート定義部分の解説なので、細かい実装などは参考文献の記事やドキュメントを参照してください。

ただ、合わせて理解が必要なので少しだけファイル内の内容と解説をします。
まずdefault.tsxですが、これはParallel Routeの表示に不可欠なものです。
これは表示しているURLと一致するSegmentが存在しない場合slot(layout.tsxに渡すmodal)にフォールバックを表示する必要があるためです。

これがないと/photosにアクセスすると404エラーとなります。
そのため、フォールバックを表示するようにする必要があります(ほとんどの場合で、フォールバックの表示は不要なのでnullとなります)

default.tsx
const Default = () => null
export default Default

また、「slotってなんやねん」という方に向けてですが、slotとはいわゆるchildrenのことです。
またはchildrenに類似するものと捉えていただければと思います。
簡単に言うとPropsとして渡されるchildrenなどのReactNodeのことを良います

以下のようにしてmodalをslotとして渡すことができます。

layout.tsx
import react from 'React'

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

const PhotosLayout: React.FC<Props> = ({ children, modal }) => {
  return (
    <main>
      <div>{children}</div>
      <div>{modal}</div>
    </main>
  )
}

おわりに

いかがでしたでしょうか?
Intercepting RouteはNext.jsでもつまずきやすいポイントが多いのかなと思います。
基本的な機能を理解していれば、使いこなせますが、結構、覚えておくことや基礎理解が重要な機能なのではと個人的には思っているので、少しでも理解が深まれば幸いです。

参考文献

https://nextjs.org/docs/app/building-your-application/routing/intercepting-routes

https://nextjs.org/docs/app/building-your-application/routing/parallel-routes

https://zenn.dev/chot/articles/88ee3dc4697e57

Discussion