本番環境でApp Rourterへ移行した感想
こんにちは、PharmaXのエンジニアのCaiです。
この記事はPharmaXのアドベントカレンダーの4日目の記事です。
仕事で既存のプロジェクトのリファクタリングを行い、諸々の課題を見つけました。 SSRがうまく利用されていないや、ReactとTypescriptの書き方に沿っていないなど、直すには手をつけなければならないことが多すぎました。
リファクタリングと言いつつも、やっていることはほど書き直しです。そこで思ったのが、「いっそのことNextのバージョンも最新のものに上げよう」でした。今回はそれをやっている際に、App Routerを色々触った感想を話そうと思います。
server side? client side?
最初はもちろんこれです。
app routerの移行は「既存のpages routerにあるファイルをapp/直下にコピペして、終わり!」という単純なことではありませんでした。App RouterではRSCのデフォルト運用によって、component設計時はserver・clientのことを意識する必要があります。
我々の従来のReactの流儀(もちろんそれに沿ってないものも含めて)に従って設計したcomponentはもちろんserver・clientのことは考慮していないし、なんならstyled-componentを使っているせいで、ほぼ全てのcomponentをclient componentとして扱ってしまいます。
全てのcomponentのclient componentとして扱う?
それはまるでtypescriptを使っているのにanyを乱用しているようなものではないかw 当然許されたことではありません。
となると最後はJSの部分だけではなく、CSSのところ、styled-componentをcss moduleに移行しつつも、既存のcomponentを設計段階からやり直すと言うかなり大きなプロジェクトとなりました。
もしかしてあなたたちは仲が悪いのか?
今回のアップデートでserver side と client sideに分かれました。かつてシステム開発がfront-endとback-endに分かれた頃のように、真っ先に次に来る課題は「では通信どうはするべきか?」です。
従来のReact APP(もしくはSPA)においてもsmart componentやdump componentみたいな、componentの切り方は色々ありました。ただ、従来はどんな切り方をしていても、それらは全部一つのAPPとして扱っていたので、recoilやreduxなどのglobal state管理ツールによって、親子関係でないcomponent同士でもデータの受け渡しは難しくはありませんでした。
ではAPP Routerの場合はどうでしょうか。 まずデータの受け渡しの手段は限られています。 SCはReact APPではないのでstateといったものは使えず、実行環境はブラウザではないからlocalStorageも当然使えません。
propsはどうでしょうか? server→client はOKでしたが、逆はできませんでした。結果、server/client間の受け渡しはcookieを利用することになりました。
cookieを使うことになると、次の課題は渡せるデータの種類も限られています。 要するに、server/client間受け渡し可能なものはSerializableなデータのみになってしまいます。 OOPにおいて特に困ったことはありませんが、functional programmingが主流となるReactにおいてダメージは少し大きいかもしれません。
「Route Handlers(page routerでいうAPI Routeのこと)ならどう?」とドキュメントを読んでいるとそう思えてくるかもしれません。 一つの手でありますが、Route Handlersを使うことになってしまいます。
そう、「なってしまう」のです。 単なる「component間のデータの受け渡し」のために、Route Handlersを使うのは本来の意図とは少しズレている気がします。
global state管理ツールにおいても、Recoilなどのatom概念が流行し、Reduxが次第に使われなくなる背景と同じ理由で、我々はただ、親子関係にないcomponent間でデータの受け渡しがしたいだけであって、アプリを丸ごとReduxに依存させたいわけではないのです。
今後の展望
大きなアップデートなので、何もかも順調とは思っていません。むしろすべて順調にいく方がおかしいでしょう。 細かい困り事は他にも色々ありますが、一応全部想定内ではあります。
度々見かける「server sideだったりclient sideだったり、use client使わないといけないと色々制限があって逆に生産性下がるじゃない?」といった声もありましたが、私の肌感としてはそんなことはありませんでした。
「実装」だけにフォーカスするともちろんそうではあります。ただTypescriptと一緒と同じです。制限事項のあるTypescriptは、当然実装面は客観的に見てJSより書きにくいのは当たり前ですが、「実装&テスト&保守」の観点から見れば、十分trade-offは取れていると考えられます。
server/client間の受け渡しの何か別のソリューションがあったらより使いやすくなるでしょう。あと 3rd partyのサポートがもっと充実にしてほしいと思っています。
現状は、RSCサポートしていないライブラリーを使用すると、その時点でcomponentがclient componentになる運命なので、server側用のtoastライブラリーなどがあったら嬉しいです。
また、そして弊社の事情ではありますが、firebaseを使用しており、server側のfirebase SDKはfirebase-admin SDKでした。ただ、server側といっても完全なNodeではないので、firebase-admin SDKは意図通りに動いてくれませんでした。この辺りもアップデートが来れば、より使いやすくはなるでしょう。
各機能を使ってみた感想の一言コメント
Server Actions
「いかに多くの処理をserver側に持っていくか?」と言うゲームにおいて、「ではsubmit処理のある登録系画面は全部clientにしないといけないのか?」という問題の救済処置のためのものです。
ただ結論から言うとほぼ実践レベルに使えません。 プロダクト設計の原則上、登録などを行う場合ユーザーにその操作の結果が成功か失敗かを知らせる必要があります(UX観点)。その際よく使われる手段はtoastです。 そしてtoastはJSで制御する場合がほとんどです。
Route Handler
serverless構成なら積極的に使っていきたいが、そうでない場合は無理に使うメリットはないかと思います。 もちろん、あなたの組織のAPI設計が酷く、フロントとして「そんなものに付き合ってられん!」と考えるのであれば、 BFFの代用品としては優秀です。
Parallel Routes
使う場面が限定的ではありますが、刺さると結構なメリットを感じられるでしょう。 また、Parallel Routes内の画面同士は基本urlのsearchParamsで情報伝達することになるので、 それさえ許容できれば。
middleware
良き。 謎のwrapファイルが減ります。
file conventions
layout.tsxやerror.tsxなどのこと。 用途に応じて積極的に取り込んでいきたいです。 component内に「なぜこれがここに書かれているんだ?」みたいなものが大分減るからです。
ただ無理に合わせる必要はありません。実際にloading.tsxではなく自前でsuspenseを書いているところもあるので、要件と相談です。
最後に
今回は、App Router移行作業の中で実際にあった細かい課題とどう解決したかについてはあまり詳細に触れなかったので、ほぼ感想文でした。ですが、これからApp Router移行しようと思っていて、
「どんなところが大変か?」
「事前調査はどこを中心的に見た方がいいか?」
「移行して本当に良かったのか?」
などを心配している方にとって、少しでも参考になれればと思います。
PhramaXでは、積極的な技術発信が文化となっていて、下記のような勉強会も行います。ご興味のある方は是非ご参加ください!
お知らせ
PharmaXの採用情報について、こちらで随時更新しております。
少しでもご興味をお持ちいただけましたら、ぜひカジュアルにお話ししましょう!