1年かけてNext.jsのapp routerへ完全移行した話
App Router移行は進んでいますか?
Next.js v13から一年以上経過しました。
ReactとNext.jsは、React Server Component(以下RSC)やServer Actionを筆頭にどんどん新機能がリリースされています。
私も仕事でNext.jsを採用しています。
App Router移行するか。するとしたらいつするか。
どのようなアーキテクチャにするか考えついに先日、全ページApp Router移行できました。
一緒に働くフロントエンドエンジニアのみなさま、工数を確保してくださったPMありがとうございました。
対象読者
- Next.jsで開発しており、Pages Routerの資産を持っている人
- これからApp Routerに移行したい人
- App Routerの移行で困っている人
なぜ移行しようと思ったか
App Routerが公開されましたがPages Routerがdeprecatedになったわけではありません。
App Routerに移行しなくても問題ありません。
しかし我々は以下の理由でApp Routerへの移行を決断しました。
- ディレクトリ構成をコロケーション化したかった
- RSCの方が考慮することが減る
- Next.jsの最新に追従した方が今後楽になると考えた
また、テックリードの私が元々spring bootなどでバックエンドを書いていたのもあって、RSCの思想に非常に惹かれていました。
詳しくは後述しますが、App Router移行を経てほとんどの部分で満足しています。
もちろんApp Router特有の仕様に悩まされることもありますが、App Routerは今も進化を続けているので、いずれ解消すると信じています。
移行プロジェクトでやってはいけないこと
いわゆるしくじり先生です。
- 依存ライブラリの対応状況を調べずに移行する
- App Routerと同時にライブラリも移行する
- App Routerらしい書き方にこだわる
一言で言うと、「移行だけでも大変だから、それ以上のことはやるな。」です。
1. 依存ライブラリの対応状況を調べずに移行する
ライブラリによってはApp RouterやRSCで動かない場合があります。
移行途中に気づいた場合、移行計画が頓挫する可能性もあります。
移行を実施する前にライブラリがRSCに対応しているか調査してください。
RSC移行時に問題になりそうなライブラリを挙げておきます。
- CSSライブラリ各種
- tailwindcss, MUIは対応済
- データフェッチライブラリ
- GETやPOSTならfetchをそのまま使うのが楽そう
- graphqlクライアントライブラリは、urqlが対応済
- グローバル状態管理ライブラリ
2. App Router移行と同時にライブラリも移行する
採用ライブラリがRSCに対応していなくてもApp Router移行と同時にライブラリ移行を進めないでください。
全てのコンポーネントにuse client
を付与してクライアントコンポーネントとして扱ってもApp Routerとして動きます。
こうすればRSCで動かないライブラリでも移行可能です。
3. App Routerらしい書き方にこだわる
App Router移行時にApp Routerらしい書き方にこだわらないでください。
App Routerらしい書き方にリファクタリングするのはApp Routerで動くようになってからで大丈夫です。
まずは動かすこと。これを最優先にしてください。
App Router移行手順
反省点も踏まえてApp Routerの移行のおすすめ手順を紹介します。
Next.jsのサンプルなどはすでに触っており、App Routerはある程度理解している前提です。
特に公式のマイグレーションガイドは一読ください。
- プロダクトにApp Routerのサンプルページを作成する
- 新規ページはApp Routerで作成する決まりにする
- 既存ページを1ページずつ
/app
に移動する - 各ページをApp Routerに最適化する
順番に説明していきます。
1. プロダクトにApp Routerを使ってサンプルページを作成する
サンプルページを作成してライブラリやLinterがApp RouterとRSCに対応しているか確認します。
Next.jsのサンプルプロジェクトとは違い開発中プロダクトにはライブラリやLinterやCIなど、色々なものがすでに入っているはずです。
それらがApp Router導入の妨げになる可能性があります。
(e.g. すでに/app
を利用していたり、App Router特有の記法がLinter違反になったり)
よく使うライブラリはサンプルページで動かしてRSCで動作確認をしてください。
データフェッチやCSSライブラリは入念に。
今利用している物がRSC未対応の場合は、移行先の検討もこのフェーズで行ってください。
移行先のライブラリがちゃんとRSCで動くか試してください。
サンプルページの作成はテックリードやApp Router移行リーダーが1人で実施するのが良いでしょう。
1人で作成できない規模のサンプルページを作成する必要はありません。
作成後はチームに共有し、一緒に画面を触るのも良いと思います。
2. 新規ページはApp Routerで作成する決まりにする
サンプルページを参考にして新規ページを作成することをチームで合意します。
App Routerらしい書き方やRSCを活かしたコードになっているのがベストですが、一番大切なのはこれ以上Pages Routerの資産を増やさないことです。
この約束が守られれば、これ以降は/pages
以下に新規ページが増えなくなります。
守れないと既存のページを移行している横で/pages
以下のコードが増えます。
移行へのモチベーションが下がるので避けてください。
移行中はPages RouterとApp Routerが同居します。
Pages RouterからApp Routerへのページ遷移は、SPAの遷移ではなく通常のURL遷移になります。
Context
ベースのグローバル状態管理ライブラリを入れている場合は注意してください。(e.g. redux, recoil, etc...)
Pages RouterとApp Router間の遷移ではContext
ベースのグローバル状態を引き継ぎません。
私のチームではContext
ベースのグローバル状態管理は使用していなかったので問題になりませんでした。
/app
に移動する
3. 既存ページを1ページずつようやく既存ページを/app
に移動していきます。
ここではApp Routerっぽい書き方には寄せません。(だから移行ではなく移動)
既存のコンポーネントにuse client
をつけて、なるべく変更量が少ない状態で/app
に移動します。
前の章でも書きましたが、App RouterはURL単位で移行可能です。
まずはサーバーから取得したデータを表示するだけのページなど、ユーザーからのinputがない画面から移行をお勧めします。
逆に現在開発が積極的に行われているページの移行は難しいです。開発が落ち着くまで待つのが賢明です。
このフェーズでは/app
にファイル移動してデグレを起こさなければ100点です。
それ以上は目指してはダメです。
/app
以下のページをApp Routerっぽい書き方にする
4. フェーズ3で移動完了したページをApp Routerっぽい書き方にしていきます。
フェーズ3を全て実施してからフェーズ4に取り掛かっても良いですし、フェーズ3とフェーズ4を交互に進めても良いです。
私が思うApp Routerっぽい書き方とは何か。
参考資料をいくつか紹介します。
-
[Next.jsのAppRouter] コロケーションパターンを実現し、eslintで依存の向きを強制する方法
- こちらの記事で紹介されているeslint-plugin-import-accessは私も利用しています
-
App Router時代のデータ取得アーキテクチャ
- 上記のeslint-plugin-import-accessの作者の方の資料です
Pages Routerではファイル名がパスとして扱われていたので、page以外のファイルを置けませんでした。
App Routerでは任意のファイル名を配置できるようになったので、コロケーションパターンの実現できます。
コロケーションパターンにすべし!とまでは言いませんが、App Routerはコロケーションパターンに向いてます。
App Routerに移行した感想
初めにも書きましたが、体感はかなり良いです。不満点はほとんどありません。
良かったところをいくつか共有します。
ユーザー視点
明らかに初期レスポンスが速いです。
従来はページのやり取りの後にデータフェッチが行われてレンダリングされていましたが、RSCになってからはページのやり取りで完結しています。
また、urqlのfragment-maskingを同時に利用するようになったのもあって、レイアウトシフトが発生しなくなりました。
また、開発者ツールで見ても明らかに通信回数が減りました。
移行前の通信回数が多いページは開くだけで10回ほどリクエストを飛ばしていましたが、RSCに寄せることで通信1回でページが表示できるようになりました。
開発視点
いわゆるDX(Developer eXperience)は非常に向上しました。
ここについては語りたいことが多いので、いっぱい書かせてください!
- ダイアログを
page.tsx
を使って記述できる - コロケーション化されて管理しやすくなった
-
layout.tsx
など痒い所に手が届く機能群
page.tsx
を使って記述できる
ダイアログを今まではダイアログを管理する場合、上位のコンポーネントでuseState
を用いて開閉時の状態を管理していました。
ダイアログ内でデータフェッチする場合、ダイアログの中にsuspense
を配置して...とかやっていると、ダイアログ1つ置くだけでページが複雑になってしまう課題を抱えていました。
App Routerのintercepting-routesを使うことで、ダイアログをRSCでページとして記述できるようになり、ダイアログがかなりスッキリしました。
コロケーション化されて管理しやすくなった
ファイルは使う場所の近くに置いた方が見やすいです。
また、配置場所でどこで利用されるコンポーネントか分かるので、実装の心理的負荷が下がりました。
汎用コンポーネントでないならば、設計が少々ずさんでもレビューを通しやすいです。
App Routerのおかげというより、App Routerとeslint-plugin-import-accessの合わせ技のおかげですね。
layout
など痒い所に手が届く機能群
URLの階層とデザインの階層はおおよそ一致していると思います。
それを表現できるlayoutや、エラー発生時のerror、loadingなど、今までいちいち自前で実装していたものをNext.jsが準備しています。
これらを活用することで記述もスッキリしましたし、面倒で省略していた処理を書くようになりました。
逆に困ったこと
cache周りはかなり複雑です。
この記事ではApp Routerのcacheについて深く記述しませんが、移行時にいくつかトラブルになることもありました。
リアルタイム性が必要になる場合はRSCに加えてクライアントサイドでもデータフェッチする判断を最終的にはしました。
それなら最初からRSC要らないのでは?と思われるかもしれません。
が、やはりそれでも初期表示が早いメリットは大きいです。
App Routerのcache周りの参考資料を置いておきます。
最後に
私とチームのApp Router移行の手順と反省点をまとめてみました。
結局発表から1年くらいかかりました。機能開発と並行だったので、工数が1年というわけではないです。
graphqlクライアントをurqlに移行したり、MUIがRSCに対応するまで待ったり、色々な段階を経て無事に移行完了できました。
私たちは運よく(先見の明があった?)Next.jsをサーバーとして動かして運用していたので、移行しやすかったのもあります。next export
していたらもっと大変だったと思います。
App Router移行は必須ではありませんが皆さんも挑戦してはいかがでしょう。
Discussion