こんにちは!
Arch Squad でインターンをしている 市古(@igsr5)です。
先日、 wantedly.com の最新Webフロントエンドリポジトリ※1を Next.js に移行しました。
この記事では実際の導入プロセスについて紹介します。
※1 Wantedly の Web フロントエンドアプリのアーキテクチャについてはこちらのハンドブックで紹介していますので是非ご覧ください。
話すこと
- Next.js 移行の背景
- Next.js の簡単な特徴
- 導入の際に行ったこと
- なぜこの導入プロセスを取ったのか
- Next.js 移行で変わったこと
話さないこと 🙅♂️
- 詳しい コード実装
- Next.js の細かい機能
Next.js に移行するまでの背景
「SSR 実装にレールを敷きたかった」
wantedly.com では SEO や SNS シェア促進などのために SSR(Server Side Rendering)実装が必要になります。しかし SSR の実装には React + Express の独自実装のものが動いており、様々な課題が存在していました。
それらの課題を「一般的に広く使われる SSR 基盤」(つまり Next.js)へ移行する ことで解決したかったのが移行の背景です。
独自 SSR の課題
- メンテナンスが難しく不具合が発生しやすい
- 一部ページを SSR にすることが困難(ほぼ強制的に全ページで SSR してしまう)
- SSR で必要なデータ取得が複雑になる ※2
- 「自然なフロントのコードを書くと SSR のパフォーマンス(サーバのレイテンシ)が悪くなる」という構造の難しさ
- Apollo Client と SSR の罠 その1 - パフォーマンス改善編 | Wantedly Engineer Blog
開発体験上の課題
- Webpack Dev Server と SSR server の2つのプロセスを起動する必要がある
※2 Wantedly では、フロントエンドからのデータ取得に GraphQL を利用しています。
詳しくは、Wantedly Engineering Handbook で紹介しているので、ぜひご覧ください。
Next.js とは

Next.js by Vercel - The React Framework
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
}
}
この機能で サーバ側でのデータ取得がかなり容易になり、またメンテナンス性も高いものになります。
HTML 配信戦略
Next.js では複数の HTML 配信戦略をサポートしています。
- CSR(Client-Side Rendering) - 空の HTML を返して、ブラウザでレンダリング
- SSR(Server-Side Rendering) - リクエストごとにサーバサイドでHTMLを組み立てて、ブラウザに返す
- SSG(Static Site Generator) - ビルド時にHTMLを組み立てて、リクエスト時にそのHTMLをブラウザに返す
- ISR(Incremental Static Regeneration) - stale-while-revaildate してくれる SSG
これらの HTML 配信戦略の中で「全体を SSR、特定のページを SSG で動かす」といったことが容易に出来ます。
また機能には関係ありませんが、公式 example 集がよく揃っているので実装の参考にすると良いです。
ここからが本題 - 導入プロセス
では、実際の導入について説明します。
やったこととしては大きく分けて
- フレームワークの検証
- 本番導入のための実装
があります。
1つ目の フレームワークの検証から説明します。
一部のページを Next.js で動かした
検証では、現在の実装をどうやって置き換えていくかを調査しました。
具体的には、Wantedly プロフィールページを Next.js に置き換えました。
(画像は筆者のプロフィールページです)
この検証では、実装の置き換え(ライブラリ、ルーティング制御)をそれぞれどうするか把握することが一番の目的でした。
例えば、react-router で行っていた History オブジェクトの制御は next-router に置き換える必要があります。その他にも ApolloClient の Next.js 上での動作などを調査しました。
対象のページを動作させ、大体の調査を終えるのに2週間を使いました。
本番に Next.js を導入する
ここからは検証を元に、さらにプロセスを細かく分けて実装しました。
それぞれのプロセスごとに master にマージし、段階的にデプロイを進めていました。
なぜこの導入プロセスを取ったか
前述の通り、今回の Next.js 移行は複数のステップに分けて行いました。
なぜこの導入プロセスを取ったのでしょうか。
一番大きな理由としては、
「一つあたりの PR (プルリクエスト) の変更量を少なくしたかった」
ことが挙げられます。では一つあたりの PR が小さくなると何が嬉しいのでしょうか。
筆者は PR を小さくするメリットを次のように考えます。
- 1つ1つの PR を小さくすることで見通しくをよくして、レビュー負荷を下げる
- 1回のデプロイで本番に与える影響をなるべく小さくしたい
反対に、一度の PR で大きな移行を行うのは、diffが大きくリスクが大きいことが分かります。
それでは各ステップについてもう少し詳しく見ていきます。
1️⃣ Next.js をインストールするだけ
このステップをおいた目的としてはパッケージの依存関係に慎重になることが挙げられます。最新の next をインストールするのに react のバージョンが足りなかったため react のアップデートも行いました。
2️⃣ 各ページの SSR を Next.js にも対応させる
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.config.js などの対応
3つ目のプロセスでは本番環境で Next.js を動作させるように実装を行いました。Wantedly のサービスは Kubernetes クラスタ上で動いています。
そのためこのプロセスの実装としては
- 本番用 Dockerfile を Next.js が動作するよう置き換える
- next.config.js の設定
あたりの対応を行いました。
またこのステップの実装は次節の 4️⃣の作業ブランチにマージさせて一つのリリースブランチとしました。
「react-router を残したままサーバを Next に切り替える」という方法もあったのですが、そうしなかった理由として、「3️⃣と 4️⃣を同時に進めていたところマージタイミングが被ったため、QA 対応のコストやリリース時期を考え1つにまとめて出した」といった背景があります。先ほど一つの PR を小さくすることのメリットを述べましたが、今回のステップのようにマージ戦略のメリット・デメリットを考え、柔軟に対応していくことも必要だと筆者は考えます。
4️⃣ ページ遷移を Next.js に対応させる
4つ目のプロセスでは ルーティング周り、つまりブラウザ側で動作するコードを Next.js に置き換えました。このプロセスでアプリケーションコードとしては完全に Next.js に移行されたことになります。
この段階で完全に本番で Next.js が動作するようになりました。
具体的には、
- 各ページのルーティングを react-router から Next.js に置き換える
- react 依存のライブラリを Next.js のものに置き換える
- e.g. react-helmet → next/head
- ページ共通のコンポーネントを Next.js の Layouts 機能 で実装する
- LP ページのヘッダ
- 各ページの GraphQL クエリの最適化
あたりの対応を行いました。最終的に react-router で設定していたルーティングは全て Next.js の pages に置き換えることができました。
5️⃣ QA で動作確認
このプロセスでは最終確認としてQA チームと連携し、見つかったバグを一つずつ無くしていきました。
その後、動作が問題ないことを確認して Next.js 移行を完了させました。
6️⃣ 社内に Next.js を周知🎉
Next.js を導入したあとは社内で Tech Lunch や速習会を通じてエンジニアの Next.js のキャッチアップを行いました。
Next.js 移行した結果
冒頭でも話しましたが、今回の Next.js 移行は SSR 実装にレールを引きたかったのが一番の目的です。
実際に Next.js に移行してみて期待通り SSR 実装にレールを引くことができました。
特に getServerSideProps を用いた SSR 時のデータフェッチは既存実装の問題を大きく改善することができました。
SSR 実装以外にも、起動するプロセスが2つから1つに減ったことやアクセスしたページ単位でビルドが走ることなど開発体験の向上にも大きく繋がりました。
また現在 wantedly.com は SSR のみで動いていますが、特定のページで SSG をするなどの戦略を検討しやすくなりました。
おわりに
この記事では、
- Next.js に移行した背景
- SSR 実装にレールを引きたかった
- 実際の導入のプロセス
- フレームワーク検証 + 複数ステップでの導入
- 導入プロセスの考え方
について紹介しました。
実際に Next.js に移行して SSR 実装、開発体験を大きく向上させることができました。
この記事が Next.js に関心のある人や実際に移行を検討している人の参考になれば幸いです。
また今回は詳しい実装の話には触れませんでしたが、ApolloClient の扱いや getServerSideProps のページ共通化などの工夫もあるのでまた別の記事で紹介できればと思います。