Twig, jQuery, Bootstrap で構成されていたシステムをNext.jsでリプレイスしたときにやったことを書く。
前提
- IE対応必須
ディレクトリ設計
を参考にした。
ユニットテスト用の__test__
は基本的に作らず、format.ts
とformat.test.ts
は同じ階層に配置するようにした。テストが存在するのかの確認コストと、書くときのハードルを極力下げるようにするため。
コンポーネント分割については、アトミックデザインを採用しようとも思ったが、結果やめて下記のような分割単位とした。
components ├── button ├── label │ ├── H1.tsx │ └── Title.tsx └── icon
アトミックデザインでいうところのatoms(原子)とmolecules(分子)までを最大として、それ以降の大きな単位はfeatures配下のcomponentsで作成する形。
アクセスログ
Webビーコンでサーバー上のアクセスログに記載。
チームへの勉強会実施
チームメンバー全員がNext.jsに精通しているのならば不要。そうでないなら実施するべし。
コードレビューしてもらえるぐらいにはメンバーの理解を進めておく必要がある。
社内テスト
リプレイスなので、本来挙動上は何も変わらないはずだが必ず差異が出てくる。そのための認識合わせも含めて社内テストの期間はしっかりとる。
あとでやることリストの作成
リプレイスをしていくにあたって重要だが後で対応しようってものが必ず出てくるためそれをメモしていくためのシートを作成しておくと心理的に良い。
GoogleAnalytics, GoogleOptimize, GoogleTagManagerの対応
SPAになると初回ページ読み込みでしかGAが発火しないため、ページ遷移してもGAにデータが送信されない。
ページ遷移ごとに発火されるようにする設定が必要となる。
GoogleTagManagerでGoogleAnalytics, GoogleOptimizeを管理している場合は以下の設定が必要となる。
GoogleAnalyticsの設定
「履歴の変更」をトリガーに加える。
GoogleOptimizeの設定
カスタムイベントの追加。Next.js側でイベントの送信をする。
// gtm.ts /** * Google Optimizeの発火 */ export const optimize = (): void => { window.dataLayer.push({ event: 'optimize.activate' }) } // _app.tsx const router = useRouter() useEffect(() => { gtm.optimize() }, [router.pathname])
独自の神クラス化しているjsファイルの移設
すでに動いているサービスなら必ずあるであろう神クラス化したファイル。jQuery、Bootstrapプラグインも使いたい放題でメンタルやられそうになるが、1つずつ対処していくしかない。魔法はない。
- 不要な処理の削除、必要な処理だけをリプレイス対象とする
- jQueryで書かれている箇所をReactで書き直す
- jQureyプラグイン, Bootstrapプラグインで書かれている箇所の代替ライブラリを探す
最悪<script dangerouslySetInnerHTML={{}} />
で直接読み込んで乗り切りれるかもしれん。
ヘッダー、フッター、サイドバーなど共通部分の作成
web componentsを使えば旧フロントエンドと新フロントエンドで共通化できる。
しかしIE未対応のため旧フロントエンドと新フロントエンドで二重管理にすることにした。最初は苦渋の選択であったが、今では二重管理にしてよかったと思えている。旧フロントに新技術の導入をすると影響範囲も大きいので、下手に共通化などせず分離しておくのが正解な気がする。風呂敷広げすぎたらあかん。
既存APIサーバーとの連携
認証をどうするかや、APIサーバーを叩く際にクライアントから直接叩くのか、Next.jsのnode.jsサーバーを経由して叩くのかなど。
node.jsサーバーを経由して叩くほうがレスポンスの一部を秘匿するなどBFFのような役割をもたせることができるので良い。既存APIがすでにオープンAPIになっているなら話は別。
$_SESSIONなどPHP特有の機能の外だし
$_SESSION
の値をテンプレートファイルの引数に渡したりしている箇所は、新フロントエンドからAPIを叩いて$_SESSION
の値を取得できるようにする。
もしくはセッションをRedisなどに保存している場合は、新フロントエンドでRedisクライアントを作成して直接取得しにいってもよい。
旧フロントエンド -> 新フロントエンドへのルーティング
nginxでやるのか、ホスティング先のルーティング機能でやるのか。
いずれにせよ注意したいのは旧フロント環境で/api
がすでに存在している場合、Next.jsのAPI Routes(/pages/api
)とバッティングする可能性がある。
ちなみにnginxでやる場合は下記の設定で可能。
nginx.conf
# 新フロント環境からのリクエスト処理。'new-front-container'は新フロントのdockerコンテナ名 location ^~ /_next { proxy_pass http://new-front-container:3000; } location ~ ^/(new_front_url) { # (new_front_url | new_front_url2)のように対象ページを追加 proxy_pass http://new-front-container:3000; }
旧フロント↔新フロントへのログイン引き継ぎ
旧フロント→新フロントの場合、PHPSESSIDを新フロント側で取得し、Redisに問い合わせる。Redisに問い合わせる際は新フロントから直接でもいいが、既存のAPIサーバーを経由して取得しsession_decode()
して新フロントに返すと言う流れのほうが楽。
新フロント→旧フロントの場合、新フロントでログイン時にCOOKIEに暗号化したログイン情報を保存しておき、旧フロント側で復元してログイン済みにするという流れ。
COOKIEに保存する際はhttpOnlyにするのは必須。
ライブラリ選定
- ユニットテスト、モック
- 日付
- 状態管理
- 通信
- OGP
- CSSフレームワーク
デプロイ先はVercelかそれ以外か
結論から言うとVercelは利用しなかった。初めのうちは「ゼロコンフィグでいくんじゃ! next/imageもISRもNext.jsの機能をフルに使いたいからVercel一択!!」となっていたのだが、冷静に考えるとなし。すでに動いているサービスの中にNext.jsを組み込むとなると、Vercelという選択肢を取ることは少ないのかなと思った。理由は下記。
- Vercelが落ちたときのリカバリーはどうするか
- VercelのProプランからEnterpriseプランに上げざるを得なくなった場合、払えるか
- 障害発生時に調査可能か、Vercelの裏側で何が起きているのか把握できるか
結局デプロイ先はAzure App Serviceにした。元々すでに動いているサービスはAzureにデプロイしていることもあり、また、障害ポイントをAzure以外のVercel(AWS)など増やしたくなかったため。
デプロイ先をVercel以外にすることのメリット・デメリット
デメリット
- next/imageが使えない
- ISRが使えない
- .next/staticなどの静的ファイルのCDN対応が別途必要となった
メリット
- 障害ポイントが減る
- IP制限やBasic認証など自由に設定できるようになる
- Vercel特有の何かを覚える/調べる必要がなくなる
パフォーマンス計測
チーム内でリプレイス前のパフォーマンスを計測し、基準を作っておくといい。リプレイス後の比較に使うため、ひいてはリプレイスしたらこんなにパフォーマンス上がりましたッと言うためである。 Webのパフォーマンス計測はGoogleが提唱しているFCP、LCP、FIDを項目として追えばいい。 リプレイス前のパフォーマンス計測にはSentryを利用した。それぞれ下記のような形で基準を出した。
FCP(要素をレンダリングするまでの時間): 3000ms以内であること LCP(コンテンツの表示速度): 3000ms以内であること FID(TBT)(ユーザーの初回操作からの反応速度): 100ms以内であること
リプレイス後の計測にはLighthouseとPageSpeed Insightsを使う。 開発時はChromeDevToolのLighthouse、デプロイしてURLでアクセスできるようになったらPageSpeed Insightsで計測する。Lighthouseは実行時の環境に依存してしまうため、PageSpeed Insightsのほうが普遍的な結果が出るはず。
終わりに
このプロジェクトは1人で完遂した。おそらく多くても2人ぐらいで進めていくのがベストだと思う。リプレイスはめちゃくちゃおもしろいので、1人でガツガツと進められるのは非常に良かった。仮に2人でやっていたとしたら熱量の差に萎えてスケジュール通り終わらなかったと思う。