READYFOR(レディーフォー)
READYFOR、React on RailsのページをNext.js App RouterのSPAへ。Emotionを残すためあえてクライアントコンポーネント主体で移行し、Lighthouseはモバイル約10点・デスクトップ約20点向上
クラウドファンディングプラットフォームを運営するREADYFORは、Railsに React on Rails を組み込んでReactコンポーネントをレンダリングしていたページを、Next.js App RouterによるSPAへ移行した。当時の React on Rails v14 ではハイドレーションが load イベント時に実行されており、SSRもExecJS上で動くという制約があった。加えて全ページのJavaScriptが1つの巨大なバンドルにまとめられ、あるページでは約690KBのうち約380KB(55%)が使われていないコードだった。社内標準のスタイリングライブラリEmotionがサーバーコンポーネントに非対応だったため、あえて「ほぼ全てのコンポーネントをクライアントコンポーネントにする」方針を取り、スタイリングライブラリの移行は切り離した。結果としてLighthouseのパフォーマンススコアはモバイルで平均約10点、デスクトップで約20点向上した。
課題
React on Rails v14はloadイベント時にハイドレーション。約690KBのJSのうち55%が未使用コード
アプローチ
Emotionを残すためあえてクライアントコンポーネント主体でApp Routerへ移植し、認証はRailsに残す
効果
Lighthouseスコアがモバイルで平均約10点、デスクトップで約20点向上。移行は順調に完了
背景
READYFORはRailsアプリケーションにReact on Railsを組み込み、Reactコンポーネントをレンダリングしていた。この構成には3つの課題があった。1つはハイドレーションのタイミングで、通常React のハイドレーションはDOM構築完了時(DOMContentLoaded)に実行されるべきだが、当時のReact on Rails v14では全リソース読み込み完了時(load)に実行されていた。ハイドレーション開始が遅れることで、パフォーマンススコア(TTI)の悪化や、useEffectで記述したクライアント処理の実行遅延という問題が発生していた。2つ目はSSRの実行環境で、React on RailsはSSRをExecJSで実行しており、Node.js APIや最新のJavaScript機能には対応していない。3つ目はバンドルで、全ページのJavaScriptが1つの巨大なバンドルにまとめられており、あるページでは約690KBのJavaScriptが読み込まれ、そのうち約380KB(55%)は使われていないコードだった。UX面では、各フィルタやソートの状態をURLに反映してリンクの共有やブックマークを可能にすること、ページ遷移やフィルタ・ソート時に画面全体をリロードせずコンテンツ一覧のみを更新することが求められていた。さらに複数の新規プロダクトの立ち上げが計画されており、主力プロダクトでNext.jsのSPAを実運用してノウハウを蓄積できれば、実績のある技術スタックを適用することで新規プロダクトの立ち上げリードタイムの短縮に期待できるという狙いもあった。
移行のプロセス
移行は「理想を追い求め過ぎずに現状に合わせた現実的なアプローチ」を軸に進められた。
- 開発方針: ほぼ全てのコンポーネントをクライアントコンポーネントにする方針を採用。社内で採用しているEmotionはランタイムCSS-in-JSのためサーバーコンポーネントではサポートされておらず、スタイリングライブラリの移行とApp Routerへの移行を同時に進めるとリスクとコストが倍増するため
- コンポーネントの流用: 移植元のReactコンポーネントはスタイリングやロジックの変更無しに可能な限りそのまま流用し、サーバーコンポーネントはデータ取得用のラッパーとして利用
- 移行対象の選定: 移行対象がほぼリードオンリーなページだったため、複雑なフォーム処理や状態管理の考慮が不要だった
- 認証・API連携: 認証およびセッション管理はRailsが一元的に担い、Next.jsはブラウザから送信されたセッションCookieをそのままRails APIに転送する構成とした
- ルーティング: フィルタやソートの状態をURLに反映するため、nuqsを活用したルーティングへ変更
- テスト方針: ナビゲーション、エラーハンドリング、ストリーミングといった機能は実際のブラウザ環境でこそ本来の動作を確認できるため、Playwrightを用いたブラウザテストを主軸に据えた。Next.jsのExperimental test mode for Playwrightを有効にし、MSWを使ってサーバーコンポーネントからのAPI通信をモックしつつブラウザ上での振る舞いを確認
- インフラ環境の整備はSREメンバーと協力して進めた
移行前後の変化
| 指標 | Before | After | |
|---|---|---|---|
| レンダリング構成 | Rails + React on Rails(SSR) | Next.js App Router(SPA) | |
| SSRの実行環境 | ExecJS(Node.js APIや最新JS機能に非対応) | Node.js | |
| ハイドレーションのタイミング | loadイベント時(React on Rails v14) | App Router標準 | |
| JavaScriptバンドル | 全ページで1つの巨大なバンドル。あるページでは約690KB中約380KB(55%)が未使用 | ルート単位で分割 | |
| Lighthouseパフォーマンススコア | — | モバイルで平均約10点、デスクトップで約20点向上 | |
| 認証・セッション管理 | Rails | Rails(Next.jsはセッションCookieをRails APIに転送) |
結果
クライアントコンポーネント主体という現実的な方針を取ったにもかかわらず、パフォーマンスは明確に改善した。
- FCP / LCP / TBT の指標が改善し、Lighthouseのパフォーマンススコアはモバイルでは平均約10点、デスクトップでは約20点向上(5ページ、各3回計測の平均値)
- Next.js特有の問題で大きくハマることはなく、移行作業は順調に進んだ
一方でトレードオフもあり、サーバーコンポーネント主体の開発と比べ、バンドルサイズ削減やハイドレーションコスト削減の恩恵が限定的だったり、RSCペイロードがHTMLに含まれるためHTMLサイズが増加するといったデメリットがあるとしている。今後はNext.jsのキャッシュ活用によるパフォーマンス改善や、スタイリングライブラリの移行といった次のステップに進むという。
学び
移行を成功させるポイントは、理想を追い求め過ぎずに現状に合わせた現実的なアプローチを取ること。App Routerの本来の強みであるサーバーコンポーネント主体の設計をあえて捨て、スタイリングライブラリの移行を別フェーズに切り離したことで、リスクとコストの二重化を避けつつ確実にパフォーマンス改善を得ている。移行対象をリードオンリーなページに絞った点も、複雑なフォーム処理や状態管理の考慮を不要にした。
