Intercepting Routesで 幸せになった話

だいぶ今更感がありますが、  を導入してのメリデメをまとめました。結論から言うと、早く導入しておけば良かったなぁという感想です。

本記事を読むにあたり

以下の方々の救えればという思いで記載しています。
  • NextJSの機構理解を膨らませたい方
  • 公式を読んでも、理解や実装で挫折した方
  • モーダルのバケツリレーでうんざりしている方
  • サーバサイドでの処理をうまく活用したい方

モチベーション

フロントエンド開発を進めていく中で、画面遷移のほかに、モーダル表示したいことがあるかと思います。例えば、
  • リストをクリックして、詳細をモーダルで表示したい。
  • リストをクリックして、詳細をモーダル上で編集したい。
  • リストに新規追加するページをモーダルで表示したい。
  • 削除等の処理の確認プロンプトをモーダルで表示したい。
などなど
モーダル自体は、 いくつかのサードパーティ( MantineMaterial UI などなど )がコンポーネントを提供してくれているので、お手頃に実装できるのですが、以下のような課題に直面していました。
  • 呼び出し元からのプロパティのバケツリレーが多い。
  • プロパティが増えすぎて、必要なプロパティか判断するのに、時間を要する。
  • ページコンポーネントとしての可読性が著しく悪いため、メンテしずらい。
  • モーダルは、クライアントサイドレンダリングなので、サーバサイドでの処理ができない。(つまり、性能的に弱い)
などなど
そのファイル、コンポーネントがどういうもので、なにを責務としているかを一目で認識する手段としては、ファイル名、コンポーネント名でしか わかりません。JsDocとかコメント という手段もあるでしょうが、メンテ追いつかなくなったり、読むのが面倒ってこともあり(少なくとも筆者は怠惰)、最適解ではないということにしておきます。
なるべく、可読性よく、拡張性よく、共有コストがかからない方法でできないかというのが私のモチベーションです。私が執筆する記事はだいたいお馴染みのモチベーションですね。

サンプル

今回は、以下のようなアプリを作りたいと思います。
  1. ToDoを閲覧できる。(/todoList)
  1. ToDoのリストの行をクリックすると、詳細ページがモーダル表示される。(/todo/view/[id])
  1. ToDoのリストの行の「編集」をクリックすると、編集ページがモーダル表示される。(/todo/edit/[id])
サンプルコードは以下です。
今回使用したライブラリは、package.json をご覧ください。
サンプルと本記事のコードと乖離がある場合がございます。ご了承ください。
コードの階層は、以下のようにします。

Parallel Routes

Intercepting Routesを使用するための準備です。  へ ルーティングすることになるのですが、初期レンダリング時点では、空っぽでいいので、 を忘れずに作成します。ないと、エラーになります。
src/app/todoList/layout.tsx
default.tsx

Intercepting Routes

 は /todoList から一つ上がって、 で受け付けるよ って意味です。
 を  とすると、  に変更できます。
まず初めに、呼び出し元であるリストのコンポーネントを実装します。タイトル部分をクリックすると、  へ、編集ボタンをクリックすると、  へ遷移するようにします。
src/app/todoList/@list/page.tsx
src/component/aggregation/todo/ToDoList.tsx
実際に、Interceptにしたいpage.tsxは以下のように書きます。フォルダ構成は独特ですが、ファイルの中身はそこまで違和感はないと思います。
今回は、現在我々が開発しているシステムのユースケースに合わせて、詳細ページをモーダル表示することとします。
! 今回は、モーダル表示にしていますが、モーダルでラップせずに直接コンポーネントを返した場合、ToDoリストの下に  が表示されます。これから分かるように、一覧ページに、詳細ページを上被せするような機構が、Intercepting Routesです。
src/app/todoList/@modal/(..)todo/view/[id]/page.tsx
UI ライブラリのModalを使用すると、URLが変わらずにモーダル表示されるイメージをお持ちの方も多いと思いますが、今回は 初期にモーダル表示されるページへのルーティングなので、URLが変更されます。通常のモーダルを閉じるだけですと、URLはそのままになります。
少々手間ですが、閉じると同時にURLを遷移前に戻すような専用のコンポーネントを作成しておきます。
src/component/ui/BackModal.tsx
実際に、リストのタイトルをクリックしてみてください。ToDoの詳細ページがモーダルで表示されることが確認できると思います。
通常モーダルで呼び出される場合のコンポーネントは、クライアントサイドで呼び出されるため、同じくクライアントでのレンダリングに限定されます。ですが、Intercepting Routesを使うことで、サーバサイドでの処理も可能になります。具体的には、表示するデータをサーバサイド取得にすることができます。

エラーハンドリング

 から  を表示できたとします。
さきほど、モーダルを閉じるときに、URLを戻さないと、そのままになるとお伝えしました。このまま、リロードすると、404に飛んでしまいます。NextがURLを叩く場合は、Intercepting Routes が発火するようですが、それ以外の場合  を見に行ってしまうので、404に遷移するようです。用途に合わせて、ハンドリングしておきましょう。

例1 呼び出し元に遷移する

src/app/todo/view/[id]/page.tsx

例2 そのままのページングを許容する

src/app/todo/view/[id]/page.tsx

終わりに

モチベーションにも記載していた問題点ですが、今回の例はだいぶシンプルなものなので、そこまで問題にはならないと思います。ですが、規模が大きくなり、ページが肥大すると、億劫になってきます。如何にメンテコストを下げるかが重要になってくるかと思います。
こう言った、Framework 機構のスキル習得には、当然ながら学習コストを伴いますが、誰でもできる実装を目指すとメンテが困難になるという意見をよく耳にします。継続的なが学習が、保守性向上に繋がると思っているので、試してみるのもよいかと思います。

[追記]

! Intercepting RoutesをNGにした例の紹介
弊社では、Intercepting Routesが勝手がいいということで、Intercepting Routesしたページのデータをサーバサイドで取得するようにしてみました。このサンプルでいうところの  を以下のような構成のコンポーネントで実装しています。
ToDoView.tsx
ToDoViewClient.tsx
データの参照まではまったく問題ないのですが、更新後に、サーバサイドから取得しているデータをキャッシュクリアしたいと思い、
  • next/cache/revalidatePath
  • next/navigation/useRouter().refresh()
を使用したところ、404が発生しました。
調べたところ、6095062213 にもあるように、Issueが立ち上がっているようです。
現時点では、解決策がないので、Intercepting Routesを使用した画面では、以下のようなルールを定めました。
  • サーバサイドでのデータ取得をする場合は、データの参照のみにする。
  • データのCUDを伴う場合は、クライアントでのデータ取得にする。