Next.js by Vercel - The React Framework
Production grade React applications that scale. The world's leading companies use Next.js by Vercel to build static and dynamic websites and web applications.
https://nextjs.org/
Photo by Kseniia Lobko on Unsplash
こんにちは!
Arch Squad でインターンをしている 市古(@igsr5)です。
先日、 wantedly.com の最新Webフロントエンドリポジトリ※1を Next.js に移行しました。
この記事では実際の導入プロセスについて紹介します。
※1 Wantedly の Web フロントエンドアプリのアーキテクチャについてはこちらのハンドブックで紹介していますので是非ご覧ください。
wantedly.com では SEO や SNS シェア促進などのために SSR(Server Side Rendering)実装が必要になります。しかし SSR の実装には React + Express の独自実装のものが動いており、様々な課題が存在していました。
それらの課題を「一般的に広く使われる SSR 基盤」(つまり Next.js)へ移行する ことで解決したかったのが移行の背景です。
独自 SSR の課題
開発体験上の課題
※2 Wantedly では、フロントエンドからのデータ取得に GraphQL を利用しています。
詳しくは、Wantedly Engineering Handbook で紹介しているので、ぜひご覧ください。
Next.js は Vercel が提供する React 製のフロントエンドフレームワークです。
本題に移る前に、いくつかの機能について簡単に説明します。
ルーティングは ディレクトリ構成によって定義します。
# /boo というページが用意される
└── pages # pages ディレクトリ配下のファイル群がルーティングとなる
└── boo.tsx
SSR 時にサーバからブラウザにデータを渡す仕組みがあります。
サーバでのみ実行されるので、ブラウザのことを考えなくて良いことが利点です。
// 公式より https://nextjs.org/docs/basic-features/data-fetching
export async function getServerSideProps(context) {
const res = await fetch(`https://.../data`)
const data = await res.json()
if (!data) {
return {
notFound: true,
}
}
return {
props: { data }, // will be passed to the page component as props
}
}
この機能で サーバ側でのデータ取得がかなり容易になり、またメンテナンス性も高いものになります。
Next.js では複数の HTML 配信戦略をサポートしています。
これらの HTML 配信戦略の中で「全体を SSR、特定のページを SSG で動かす」といったことが容易に出来ます。
また機能には関係ありませんが、公式 example 集がよく揃っているので実装の参考にすると良いです。
では、実際の導入について説明します。
やったこととしては大きく分けて
があります。
1つ目の フレームワークの検証から説明します。
検証では、現在の実装をどうやって置き換えていくかを調査しました。
具体的には、Wantedly プロフィールページを Next.js に置き換えました。
(画像は筆者のプロフィールページです)
この検証では、実装の置き換え(ライブラリ、ルーティング制御)をそれぞれどうするか把握することが一番の目的でした。
例えば、react-router で行っていた History オブジェクトの制御は next-router に置き換える必要があります。その他にも ApolloClient の Next.js 上での動作などを調査しました。
対象のページを動作させ、大体の調査を終えるのに2週間を使いました。
ここからは検証を元に、さらにプロセスを細かく分けて実装しました。
それぞれのプロセスごとに master にマージし、段階的にデプロイを進めていました。
前述の通り、今回の Next.js 移行は複数のステップに分けて行いました。
なぜこの導入プロセスを取ったのでしょうか。
一番大きな理由としては、
「一つあたりの PR (プルリクエスト) の変更量を少なくしたかった」
ことが挙げられます。では一つあたりの PR が小さくなると何が嬉しいのでしょうか。
筆者は PR を小さくするメリットを次のように考えます。
反対に、一度の PR で大きな移行を行うのは、diffが大きくリスクが大きいことが分かります。
それでは各ステップについてもう少し詳しく見ていきます。
このステップをおいた目的としてはパッケージの依存関係に慎重になることが挙げられます。最新の next をインストールするのに react のバージョンが足りなかったため react のアップデートも行いました。
2つ目のプロセスでは SSR 実装を Next.js で実装しました。具体的には、「とりあえず Next.js が renderToString できる」と「各ページの getServerSideProps を実装する」の2つの状態を順番に達成しました。この段階では本番は既存の実装が動いており、手元で Next.js、既存の実装が両方動くようになっています。
とりあえず Next.js が renderToString できる
まずはプロフィールページを Next.js で動かせるように実装しました。プロフィールページはフレームワーク検証の時に大体の実装を行っています。以前の検証をもとに細かい部分を補いながらとりあえず1つのページが Next.js で動作する状態まで持っていきました。
各ページの getServerSideProps を実装する
ここまででプロフィールページは Next.js で動作するようになったのであとは LP ページ など他のページも同様に Next.js で動くようにします。このステップでは実際のルーティングは react-router が行っており、Next.js は単一のページを全 URL パスに対して返しています。
// src/pages/[...slugs].tsx
...
export const getServerSideProps: GetServerSideProps = (ctx) => {
// ここで Appolo Client の初期化や各ページで必要になるクエリのキャッシュを行う
return props;
}
やったこととしては各ページの renderToString を正常に動かすためにサーバ側で適切に GraphQL クエリをキャッシュさせました。各ページで必要なクエリを洗い出し、適切なキャッシュ処理を[...slugs].tsx に
追加することで全ページの renderToString が Next.js で動作するようになりました。
またこのステップをおいた目的は前述の 「PR を小さくすること」なのですが、そのための工夫として SSR 機構を既存の実装でも Next.js 実装でも動くように設計しました。それによって3️⃣のk8s 対応を待たずにこのステップを本番に出すことを可能にしています。
3つ目のプロセスでは本番環境で Next.js を動作させるように実装を行いました。Wantedly のサービスは Kubernetes クラスタ上で動いています。
そのためこのプロセスの実装としては
あたりの対応を行いました。
またこのステップの実装は次節の 4️⃣の作業ブランチにマージさせて一つのリリースブランチとしました。
「react-router を残したままサーバを Next に切り替える」という方法もあったのですが、そうしなかった理由として、「3️⃣と 4️⃣を同時に進めていたところマージタイミングが被ったため、QA 対応のコストやリリース時期を考え1つにまとめて出した」といった背景があります。先ほど一つの PR を小さくすることのメリットを述べましたが、今回のステップのようにマージ戦略のメリット・デメリットを考え、柔軟に対応していくことも必要だと筆者は考えます。
4つ目のプロセスでは ルーティング周り、つまりブラウザ側で動作するコードを Next.js に置き換えました。このプロセスでアプリケーションコードとしては完全に Next.js に移行されたことになります。
この段階で完全に本番で Next.js が動作するようになりました。
具体的には、
あたりの対応を行いました。最終的に react-router で設定していたルーティングは全て Next.js の pages に置き換えることができました。
このプロセスでは最終確認としてQA チームと連携し、見つかったバグを一つずつ無くしていきました。
その後、動作が問題ないことを確認して Next.js 移行を完了させました。
Next.js を導入したあとは社内で Tech Lunch や速習会を通じてエンジニアの Next.js のキャッチアップを行いました。
冒頭でも話しましたが、今回の Next.js 移行は SSR 実装にレールを引きたかったのが一番の目的です。
実際に Next.js に移行してみて期待通り SSR 実装にレールを引くことができました。
特に getServerSideProps を用いた SSR 時のデータフェッチは既存実装の問題を大きく改善することができました。
SSR 実装以外にも、起動するプロセスが2つから1つに減ったことやアクセスしたページ単位でビルドが走ることなど開発体験の向上にも大きく繋がりました。
また現在 wantedly.com は SSR のみで動いていますが、特定のページで SSG をするなどの戦略を検討しやすくなりました。
この記事では、
について紹介しました。
実際に Next.js に移行して SSR 実装、開発体験を大きく向上させることができました。
この記事が Next.js に関心のある人や実際に移行を検討している人の参考になれば幸いです。
また今回は詳しい実装の話には触れませんでしたが、ApolloClient の扱いや getServerSideProps のページ共通化などの工夫もあるのでまた別の記事で紹介できればと思います。