every Tech Blog

株式会社エブリーのTech Blogです。

Next.jsのPages RouterからApp Routerへの移行に挑戦してみた

初めまして,トモニテ開発部でSoftware Engineer(SE)をしている鈴木です.
SEチームはAPI開発からそのAPIを利用したweb開発まで幅広い領域を担当しており,トモニテ開発部のweb開発には Next.js を採用しています.
また,エブリーの開発部では定期的に挑戦week(※)なるものを開催し,技術的観点から事業貢献を行う1週間を設けており,その中でNext.jsのPages RouterApp Routerに移行する機会を頂けたので紹介させていただきます.

※ 挑戦weekについては以下のオウンドメディアで記事にしています.今回の挑戦weekは事業部の垣根を超えたチーム編成で行い,大変面白いものでした!

エンジニアが楽しみながら開発体験を向上させる「挑戦week」を実施しました!

背景

Next.jsでルーティングを行う際, v13以前はPages Routerを利用する必要がありましたが,v13以降はApp Routerという仕組みが選択肢に加わりました.
App RouterはReactの最新機能(React Server ComponentsSuspenseなど)に追従したルーティングシステムであることから,Next.js公式には新しいアプリケーションを作成する際にはApp Routerの利用が推奨されており,既にApp Routerでしか利用出来ない機能の開発も見受けられます.
これらのことから,幾つかのメジャーアップデートを経てApp RouterがNext.jsのルーティングシステムの主流になることが予想されるため,長期にわたりNext.jsの最新機能に追従していくためにはPages RouterからApp Routerへの移行が必要だと判断し挑戦する運びになりました.

移行対象

影響の大きさを鑑みて,移行対象にはトモニテで利用しているdashboardを採用しました.

移行の流れ

1. Pages Routerの各ページをApp Routerのページに変換するスクリプトの作成

当然ですが,App Routerへ移行するためにはPages Routerのページを移行することが必須です.
Pages RouterとApp Routerは共存が可能なため,1ページずつ移行していくことが出来るのですが,一つずつ置換していくと移行を終えるまでにかなりの時間を要してしまうことや,dashboard以外にもApp Routerに移行したい対象があるため,一括で置換するスクリプトを作成することになりました.
公式からはCodemodsの置換スクリプトは提供されていないため,Pages Routerの各ページに対し以下のルールに基づいた変換をするスクリプトを自作しました(※).
手間は要しますが実装に困難な点はそれほど無いかと思います.

  • use clientの付与
  • [xxxx].tsx[xxxx]/page.tsに変換
  • xxxx/index.tsxxxxx/page.tsxに変換
  • xxxx.tsxxxxx/page.tsxに変換

※参考程度になりますがGistで公開しています

2. ログイン前後のレイアウトの作成

トモニテのdashboardはログイン前後でレイアウトが異なるため,それぞれのレイアウトを作成しました.
dashboardはログイン前にしかアクセス出来ないページとログイン後にしかアクセス出来ないページに二分されていたため, Route Groups という機能を用いることで比較的簡単にレイアウトの出し分けを実現出来ました.
以下のように括弧で囲まれたディレクトリ名をつけることでRoute Groupsを利用出来ます.

app
├── (loggined)
│   ├── layout.tsx
│   ...
└── (unloggined)
    ├── layout.tsx
    ...

3. _app.tsx及び_document.tsxと等価な処理の再現

App Routerでは_app.tsx_document.tsxが廃止されたため,それらが行っていたことと等価なことをfile conventionsmiddlewareを用いて再現する必要があります.
トモニテのdashboardでは_app.tsxでsessionの管理やレイアウトの出し分けなどを,_document.tsxではヘッダーの管理などを行っていたため,middlewareとlayout.tsxを用いて再現しました.
注意点として,App RouterではHeaderコンポーネントが廃止されており,代わりにlayout.tsxからMetadataオブジェクトをexportするようになっています.このMetadataオブジェクトですが,Headerコンポーネントを用いて実現していたこと全てが再現出来る訳でなく,例えばCDNからstylesheetを読み込むことが出来なくなっています.この点については,closeはされていますがissueにもなっており解決するのには時間がかかりそうであることと,幸いなことにファイルのimportで解決できるもの以外は影響が小さかったため後回しにすることになりました.

4. ルーティングに関するhooksの移行

App Routerではルーティングに関するhooksをnext/routerではなくnext/navigationからimportするように変更されており,useRouterhookが持っていた役割もuseRouterusePathnameuseSearchParamsの3つのhooksに分解されています.従って,画像のように,next/routerを用いてrouter.pushと記述していたコードはnext/navigationのuseRouterを,router.queryと記述していたコードはuseSearchParamsを用いて置換する必要があります.
useRouterの利用箇所自体は多岐に渡っていたため,修正にはだいぶ手間が必要となりました.
また,この際,従来のuseRouterが持っていたevents等の一部の機能がnext/navigationでは実装されていないことに注意が必要です.eventsが実装されていないことから,Pages Routerで簡単に実現出来ていたページ遷移中の処理をApp Routerでは簡単には実現出来なくなりました.
公式からはusePathnameとuseSearchParamsを利用したコンポーネントを作成することによる再現方法が提示されており,これを用いれば再現可能かと思われますが,トモニテのdashboardではページ遷移の進捗を表示するためのプログレスバーにしか用いておらず,進捗を確認するニーズの有る程重いページが存在しなかったため,再現せずに先に進むことになりました.

ルーティングに関するhookの遷移
ルーティングに関するhookの遷移

5. その他

React Server ComponentsではRuntime Configが利用出来ないため,環境変数で置き換えるなどの移行作業をしました.

移行作業を通して感じたポイント

移行作業を通して,_app.tsx及び_document.tsxの大きさが移行コストの大部分を決定するように感じました.
これらのファイルで行っている事が大きいほどApp Routerのfile conventionsやmiddlewareに対する知識と実装経験が求められるためです.
また出来なくなることや,再現に一手間が必要になることを整理した上で移行に望むと見通しがつきやすいように思います.

まとめ

「Next.jsのPages RouterからApp Routerへの移行に挑戦してみた」と題し,実際に挑戦week中に行った移行作業について紹介させていただきました.
後回しにしたものなど一部やり残しはありつつも,主要部の移行自体は出来たのではないかなと思っております.
何より,移行作業を通してfile conventionsやRoute GroupsなどのApp Routerでのみ利用出来る機能を学び,利用することが出来,知的好奇心が刺激された一週間でした.
アプリケーションの規模や利用している機能に依存しますが,移行する際にはおおよそ同様の流れを取るのではないかと感じており,この記事がPages RouterからApp Routerへの移行を検討されている開発者の方々のお役に立てたら大変嬉しいです.
ここまでお読みいただきありがとうございました!