App Routerの最適なデザインパターンを考えてみた

はじめに

Next.js v13 から導入された App Router に、せっかくなので Page Router から移行してみました。
どうも単純な移行とはいかなかったので、今後のバージョンアップにも耐えられる最適なデザインパターン を考えてみました。
App Router については、 公式サイトをご覧ください。
従来、Page Router では、Container/Presenter パターンで実装していました。
これにより、コンポーネントの再利用性、可読性を高めることができました。
ところが、App Router で このパターンをそのまま利用すると
App Router の恩恵を受けることができないので、どう実装するかが課題になります。

Container/Presenter パターン

まずは、サンプルのソースコードを見てください。(import は省略)
pages/sample/index.tsx
pages/sample/SampleContainer.tsx
pages/sample/SamplePresenter.tsx
 配下に  を配置することで、ルーティングが可能です。
もちろん、 に Container と Presenter など分けずに記述しても問題ないですが、
ここでは、以下の責務に応じて階層分けします。
  • index: ルーティング
  • Container: ロジック
  • Presenter: ビュー
今回は、3 層構造にしてますが、 のような共通レイアウトを  から隠蔽するため、 さらに階層を増やしてもよいです。

App Router の導入

App Router を適用すると、以下のようになります。
app/layout.tsx
app/sample/page.tsx
app/sample/SampleContainer.tsx
①:Page Router の場合には、それぞれの  に実装が必要でしたが、App Router の場合には、 に実装するだけで、全てのページに適用されます。仮に、 ページ固有のレイアウトがあれば、 を実装するだけで実現できます。
②:Page Router の時と同様に、このファイルはルーティングが責務のため、呼び出しのみに留めます。
③:忘れてならないのは、Client コンポーネントと Server コンポーネントの棲み分けです。
この場合、  の中で  を使っているので、このコンポーネントは Client でレンダリングされます。逆に、 を使っていないコンポーネントは、Server でレンダリングされます。

改善点

 をよくみると、Client に依存しないタイトル部分  も含まれているため、全量がレンダリングされます。このサンプルだと、少量なのでそこまで問題にはなりませんが、ページの規模が大きくなると、無駄なレンダリングをすることになります。
そこで、以下のモチベーションで、綺麗な分割ができないか考えてみました。
  1. ロジック部分とビュー部分の分離を維持する
  1. Client/Server の棲み分けを明確にする
  1. 再利用しやすい構造にする
その結果、以下のような構成に辿り着きました。
app/layout.tsx
app/sample/page.tsx
app/sample/layout.tsx
app/sample/SampleContainer.tsx
app/sample/SamplePresenter.tsx
ファイル数は増えましたが、  及び、ここから呼び出される  が、Client でレンダリングされるようになりました。
さらに、 は、従来ページのタイトルまで含んでいたので、再利用しにくい状態でしたが、 に切り出すことで、再利用しやすい構造になりました。
これは、ページのタイトルは、ページが決定するものであり、パーツに相当するコンポーネントが決定するものではないという 責務分割を意識した結果です。
ただ、規模が大きくなり  も Client レンダリングを回避したい場合があるかもしれません。その場合は、Container に対して、Props 経由で Presenter を渡すことも可能です。今回は、簡単なサンプルにするため、割愛します。
  • page: ルーティング
  • layout: ページレイアウト
  • Container: ロジック
  • Presenter: ビュー

まとめ

フロントエンドのフレームワークは、エンジニアの勉強速度が追いつかないくらい、日々進化しています。そのため、フレームワークの変更に追従するために、フレームワークに依存しないコードを書くことが重要です。
今回、考えたデザインパターンはあくまでも、App Router に合わせた、現時点での最適例 として参考にしていただければと思います。
仕様変更についても執筆しているので、よろしければご覧ください。