Next.jsを使用した大規模なEコマースWebサイトの再構築(ケーススタディ)

公開: 2022-03-10
簡単なまとめ↬Next.jsを使用して、従来の統合eコマースプラットフォームからヘッドレスプラットフォームに切り替えました。 Next.jsを使用して大規模なeコマースサイトを再構築する際に学んだ最も重要な教訓は次のとおりです。

私たちの会社であるUnplatformでは、何十年にもわたってeコマースサイトを構築してきました。 それらの年月の間、テクノロジースタックは、いくつかのマイナーなJavaScriptとCSSを備えたサーバーレンダリングページから本格的なJavaScriptアプリケーションに進化するのを見てきました。

eコマースサイトに使用したプラットフォームはASP.NETに基づいており、訪問者がより多くのインタラクションを期待し始めたときに、フロントエンドにReactを追加しました。 ASP.NETのようなサーバーWebフレームワークの概念とReactのようなクライアント側Webフレームワークの概念を組み合わせると、事態はさらに複雑になりますが、このソリューションには非常に満足しています。 それは、トラフィックが最も多い顧客と一緒に本番環境に移行するまでのことでした。 ライブに移行した瞬間から、パフォーマンスの問題が発生しました。 コアWebバイタルは重要であり、eコマースではさらに重要です。 このデロイトの調査:Milliseconds Make Millionsでは、調査員は37の異なるブランドのモバイルサイトデータを分析しました。 その結果、パフォーマンスが0.1秒向上すると、コンバージョンが10%増加する可能性があることがわかりました。

パフォーマンスの問題を軽減するために、多くの(予算外の)サーバーを追加し、リバースプロキシにページを積極的にキャッシュする必要がありました。 これには、サイトの機能の一部を無効にする必要さえありました。 非常に複雑で高価なソリューションができあがり、場合によっては静的に一部のページを提供することになりました。

明らかに、 Next.jsについて知るまで、これは正しく感じられませんでした。 Next.jsはReactベースのWebフレームワークであり、静的にページを生成できますが、サーバー側のレンダリングを使用することもできるため、eコマースに最適です。 VercelやNetlifyなどのCDNでホストできるため、レイテンシが低くなります。 VercelとNetlifyは、サーバーサイドレンダリングにもサーバーレス機能を使用します。これは、スケールアウトするための最も効率的な方法です。

課題

Next.jsを使用した開発は素晴らしいですが、確かにいくつかの課題があります。 Next.jsでの開発者の経験は、あなたが経験する必要があるものです。 作成したコードはブラウザで即座に視覚化され、生産性は飛躍的に向上します。 生産性に集中しすぎてコードの保守性を無視する可能性があるため、これもリスクです。 時間の経過とともに、これとJavaScriptの型指定されていない性質により、コードベースが劣化する可能性があります。 バグの数が増え、生産性が低下し始めます。

また、実行時の面でも難しい場合があります。 コードのわずかな変更により、パフォーマンスやその他のコアWebバイタルが低下する可能性があります。 また、サーバー側のレンダリングを不注意に使用すると、予期しないサービスコストが発生する可能性があります。

これらの課題を克服することで学んだ教訓を詳しく見てみましょう。

  1. コードベースをモジュール化する
  2. コードをリントしてフォーマットする
  3. TypeScriptを使用する
  4. パフォーマンスの計画とパフォーマンスの測定
  5. 品質ゲートにパフォーマンスチェックを追加する
  6. 自動テストを追加する
  7. 依存関係を積極的に管理する
  8. ログ集約サービスを使用する
  9. Next.jsの書き換え機能により、段階的な採用が可能になります
ジャンプした後もっと! 以下を読み続けてください↓

教訓:コードベースをモジュール化する

Next.jsのようなフロントエンドフレームワークにより、最近は簡単に始めることができます。 npx create-next-appを実行するだけで、コーディングを開始できます。 しかし、注意を怠り、デザインを考えずにコードを叩き始めると、大きな泥だんごになってしまう可能性があります。

npx create-next-appを実行すると、次のようなフォルダー構造になります(これは、ほとんどの例の構造でもあります)。

 /public logo.gif /src /lib /hooks useForm.js /api content.js /components Header.js Layout.js /pages Index.js

同じ構造を使い始めました。 より大きなコンポーネント用にcomponentsフォルダーにいくつかのサブフォルダーがありましたが、ほとんどのコンポーネントはルートコンポーネントフォルダーにありました。 このアプローチには何の問題もありません。小規模なプロジェクトには問題ありません。 しかし、私たちのプロジェクトが成長するにつれて、コンポーネントとそれらが使用される場所について推論することが難しくなりました。 まったく使用されなくなったコンポーネントも見つかりました。 また、どのコードが他のどのコードに依存するべきかについての明確なガイダンスがないため、大きな泥だんごを促進します。

これを解決するために、コードベースをリファクタリングし、技術的な概念ではなく、機能モジュール(NPMモジュールのようなもの)ごとにコードをグループ化することにしました。

 /src /modules /catalog /components productblock.js /checkout /api cartservice.js /components cart.js

この小さな例では、チェックアウトモジュールとカタログモジュールがあります。 このようにコードをグループ化すると、発見しやすくなります。フォルダ構造を確認するだけで、コードベースにどのような機能があり、どこにあるかが正確にわかります。 また、依存関係について推論するのがはるかに簡単になります。 以前の状況では、コンポーネント間に多くの依存関係がありました。 カタログコンポーネントにも影響を与えるチェックアウトの変更を求めるプルリクエストがありました。 これにより、マージの競合が増加し、変更が困難になりました。

私たちにとって最も効果的な解決策は、モジュール間の依存関係を最小限に抑え(本当に依存関係が必要な場合は、一方向であることを確認してください)、すべてを結び付ける「プロジェクト」レベルを導入することでした。

 /src /modules /common /atoms /lib /catalog /components productblock.js /checkout /api cartservice.js /components cart.js /search /project /layout /components /templates productdetail.js cart.js /pages cart.js

このソリューションの視覚的な概要:

モジュール化されたプロジェクトの例の概要
モジュール化されたプロジェクトの例の概要(大規模なプレビュー)

プロジェクトレベルには、eコマースサイトとページテンプレートのレイアウトのコードが含まれています。 Next.jsでは、ページコンポーネントは慣例であり、結果として物理ページになります。 私たちの経験では、これらのページは同じ実装を再利用する必要があることが多いため、「ページテンプレート」の概念を導入しました。 ページテンプレートは、さまざまなモジュールのコンポーネントを使用します。たとえば、商品詳細ページテンプレートは、カタログのコンポーネントを使用して商品情報を表示しますが、チェックアウトモジュールのカートコンポーネントに追加することもできます。

機能モジュールで再利用する必要のあるコードがまだあるため、共通のモジュールもあります。 一貫したルックアンドフィールを提供するために使用されるReactコンポーネントである単純なアトムが含まれています。 また、インフラストラクチャコードも含まれており、特定の一般的な反応フックまたはGraphQLクライアントコードについて考えてみてください。

警告コードが絡まないように、共通モジュールのコードが安定していることを確認し、ここにコードを追加する前に常によく考えてください。

マイクロフロントエンド

さらに大きなソリューションやさまざまなチームで作業する場合は、アプリケーションをいわゆるマイクロフロントエンドにさらに分割することが理にかなっています。 つまり、これは、アプリケーションを、異なるURLで個別にホストされる複数の物理アプリケーションにさらに分割することを意味します。 例: checkout.mydomain.comおよびcatalog.mydomain.com。 これらは、プロキシとして機能する別のアプリケーションによって統合されます。

Next.jsの書き換え機能はこれに最適であり、このように使用することは、いわゆるマルチゾーンによってサポートされています。

マルチゾーン設定の例
マルチゾーン設定の例(大プレビュー)

マルチゾーンの利点は、すべてのゾーンが独自の依存関係を管理することです。 また、コードベースを段階的に進化させることも簡単になります。Next.jsまたはReactの新しいバージョンがリリースされた場合、コードベース全体を一度にアップグレードする代わりに、ゾーンを1つずつアップグレードできます。 マルチチーム組織では、これによりチーム間の依存関係を大幅に減らすことができます。

参考文献

  • 「Next.jsプロジェクト構造」、Yannick Wittwer、中
  • 「Next.jsプロジェクトを柔軟かつ効率的な方法で構築するための2021年のガイド」、Vadorequest、Dev.to。
  • 「マイクロフロントエンド」、Michael Geers

教訓:コードのリントとフォーマット

これは以前のプロジェクトで学んだことです。同じコードベースで複数の人と作業し、フォーマッターを使用しない場合、コードはすぐに非常に一貫性がなくなります。 コーディング規約を使用してレビューを行っている場合でも、すぐにさまざまなコーディングスタイルに気づき始め、コードの印象が乱雑になります。

リンターは潜在的な問題についてコードをチェックし、フォーマッターはコードが一貫した方法でフォーマットされていることを確認します。 私たちはESLint&prettierを使用しており、それらは素晴らしいと思います。 コーディングスタイルについて考える必要がないため、開発中の認知的負荷が軽減されます。

幸い、Next.js 11はESLintをすぐにサポートするようになり(https://nextjs.org/blog/next-11)、npxnextlintを実行することで非常に簡単にセットアップできるようになりました。 Next.jsのデフォルト設定が付属しているため、これにより多くの時間を節約できます。 たとえば、ReactのESLint拡張機能ですでに構成されています。 さらに良いことに、アプリケーションのコアWebバイタルに影響を与える可能性のあるコードの問題を発見する新しいNext.js固有の拡張機能が付属しています。 後の段落で、コアWebバイタルを誤って傷つける製品にコードをプッシュするのを防ぐのに役立つ品質ゲートについて説明します。 この拡張機能はフィードバックをより速く提供し、素晴らしい追加になります。

参考文献

  • 「ESLint」、Next.jsドキュメント
  • 「ESLint」公式サイト

教訓:TypeScriptを使用する

コンポーネントが変更およびリファクタリングされると、一部のコンポーネントプロップが使用されなくなったことに気付きました。 また、場合によっては、コンポーネントに渡される小道具のタイプが欠落しているか、正しくないためにバグが発生しました。

TypeScriptはJavaScriptのスーパーセットであり、型を追加します。これにより、コンパイラーは、ステロイドのリンターのように、コードを静的にチェックできます。

プロジェクトの開始時には、TypeScriptを追加することの価値は実際にはわかりませんでした。 不必要な抽象化だと感じました。 しかし、同僚の1人はTypeScriptで良い経験をしていて、試してみるように私たちを説得しました。 幸い、Next.jsはすぐに使用できる優れたTypeScriptサポートを備えており、TypeScriptを使用するとソリューションに段階的に追加できます。 つまり、コードベース全体を一度に書き直したり変換したりする必要はありませんが、すぐに使用を開始して、残りのコードベースをゆっくりと変換することができます。

コンポーネントをTypeScriptに移行し始めると、コンポーネントや関数に間違った値が渡されるという問題がすぐに見つかりました。 また、開発者のフィードバックループが短くなり、ブラウザーでアプリを実行する前に問題が通知されます。 私たちが見つけたもう1つの大きな利点は、コードのリファクタリングがはるかに簡単になることです。コードが使用されている場所を簡単に確認でき、未使用のコンポーネントの小道具やコードをすぐに見つけることができます。 つまり、TypeScriptの利点は次のとおりです。

  1. バグの数を減らします
  2. コードのリファクタリングが簡単になります
  3. コードが読みやすくなります

参考文献

  • 「TypeScript」、Next.jsドキュメント
  • TypeScript、公式ウェブサイト

教訓:パフォーマンスの計画とパフォーマンスの測定

Next.jsは、静的生成とサーバー側レンダリングというさまざまなタイプの事前レンダリングをサポートしています。 最高のパフォーマンスを得るには、ビルド時に発生する静的生成を使用することをお勧めしますが、これが常に可能であるとは限りません。 在庫情報を含む製品詳細ページを考えてみてください。 この種の情報は頻繁に変更され、毎回ビルドを実行しても拡張性が高くありません。 幸い、Next.jsは、インクリメンタル静的再生(ISR)と呼ばれるモードもサポートしています。このモードでは、ページは静的に生成されますが、バックグラウンドでx秒ごとに新しいページが生成されます。 このモデルは、大規模なアプリケーションに最適であることがわかりました。 パフォーマンスは依然として優れており、サーバー側のレンダリングよりも必要なCPU時間が少なく、ビルド時間が短縮されます。ページは最初のリクエストでのみ生成されます。 追加するページごとに、必要なレンダリングの種類を考える必要があります。 まず、静的生成を使用できるかどうかを確認します。 そうでない場合は、インクリメンタル静的再生に進みます。それが不可能な場合でも、サーバー側のレンダリングを使用できます。

Next.jsは、ページにgetServerSidePropsメソッドとgetInitialPropsメソッドがないことに基づいて、レンダリングのタイプを自動的に決定します。 間違いを犯しやすいため、ページが静的に生成されるのではなく、サーバー上にレンダリングされる可能性があります。 Next.jsビルドの出力には、どのページがどのタイプのレンダリングを使用しているかが正確に示されるため、必ずこれを確認してください。 また、本番環境を監視し、ページのパフォーマンスと関連するCPU時間を追跡するのにも役立ちます。 ほとんどのホスティングプロバイダーはCPU時間に基づいて料金を請求します。これは、不快な驚きを防ぐのに役立ちます。 これを監視する方法については、学習したレッスン:ログ集計サービスの段落を使用して説明します。

バンドルサイズ

良好なパフォーマンスを得るには、バンドルサイズを最小化することが重要です。 Next.jsには、自動コード分割など、すぐに使用できる多くの機能があります。 これにより、必要なJavaScriptとCSSのみがすべてのページに読み込まれるようになります。 また、クライアントとサーバーに対して異なるバンドルを生成します。 ただし、これらに注意を払うことが重要です。 たとえば、JavaScriptモジュールを間違った方法でインポートすると、サーバーのJavaScriptがクライアントバンドルに含まれる可能性があり、クライアントバンドルのサイズが大幅に増加し、パフォーマンスが低下します。 NPMの依存関係を追加すると、バンドルサイズにも大きな影響を与える可能性があります。

幸い、Next.jsにはバンドルアナライザーが付属しており、どのコードがバンドルのどの部分を占めるかを把握できます。

webpackバンドルアナライザーは、バンドル内のパッケージのサイズを表示します
webpackバンドルアナライザーは、バンドル内のパッケージのサイズを表示します(大プレビュー)

参考文献

  • 「Next.js+Webpack Bundle Analyzer」、Vercel、GitHub
  • 「データフェッチ」、Next.jsドキュメント

教訓:品質ゲートにパフォーマンスチェックを追加する

Next.jsを使用する大きな利点の1つは、ページを静的に生成し、アプリケーションをエッジ(CDN)にデプロイできることです。これにより、優れたパフォーマンスとWebVitalsが実現します。 Next.jsのような優れたテクノロジーを使用しても、優れた灯台スコアを取得して維持することは非常に難しいことを学びました。 本番環境にいくつかの変更を加えた後、灯台のスコアが大幅に低下することが何度もありました。 制御を取り戻すために、品質ゲートに自動灯台テストを追加しました。 このGithubアクションを使用すると、プルリクエストに灯台テストを自動的に追加できます。 Vercelを使用しており、プルリクエストが作成されるたびに、VercelはそれをプレビューURLにデプロイし、Githubアクションを使用してこのデプロイメントに対して灯台テストを実行します。

Githubプルリクエストでの灯台の結果の例
Githubプルリクエストでの灯台の結果の例(大プレビュー)

自分でGitHubアクションを設定したくない場合、またはこれをさらに進めたい場合は、DebugBearなどのサードパーティのパフォーマンス監視サービスを検討することもできます。 Vercelは、本番デプロイメントのコアWebVitalsを測定する分析機能も提供します。 Vercel Analyticsは実際に訪問者のデバイスから測定値を収集するため、これらのスコアは実際に訪問者が経験しているものです。 執筆時点では、VercelAnalyticsは本番環境でのみ機能します。

教訓:自動テストの追加

コードベースが大きくなると、コードの変更によって既存の機能が破損した可能性があるかどうかを判断するのが難しくなります。 私たちの経験では、セーフティネットとして優れたエンドツーエンドのテストセットを用意することが重要です。 小さなプロジェクトであっても、少なくともいくつかの基本的なスモークテストを行うと、生活がとても楽になります。 私たちはこれにサイプレスを使用しており、絶対に気に入っています。 NetlifyまたはVercelを使用してプルリクエストを一時的な環境に自動的にデプロイすることと、E2Eテストを実行することの組み合わせは貴重です。

cypress-io/GitHub-actionを使用して、プルリクエストに対してサイプレステストを自動的に実行します。 構築しているソフトウェアのタイプによっては、EnzymeまたはJESTを使用してより詳細なテストを行うことも有益な場合があります。 トレードオフは、これらがコードとより緊密に結合されており、より多くのメンテナンスが必要になることです。

Githubプルリクエストの自動チェックの例
Githubプルリクエストの自動チェックの例(大規模なプレビュー)

学んだ教訓:依存関係を積極的に管理する

依存関係の管理には時間がかかりますが、大規模なNext.jsコードベースを維持する場合は非常に重要なアクティビティです。 NPMはパッケージの追加をとても簡単にし、最近はすべてのパッケージがあるようです。 振り返ってみると、多くの場合、新しいバグを導入したり、パフォーマンスが低下したりしたときは、新しいまたは更新されたNPMパッケージと関係がありました。

したがって、パッケージをインストールする前に、常に次のことを自問する必要があります。

  • パッケージの品質はどれくらいですか?
  • このパッケージを追加すると、バンドルサイズにどのような意味がありますか?
  • このパッケージは本当に必要ですか、それとも代替手段がありますか?
  • パッケージはまだアクティブに維持されていますか?

バンドルサイズを小さく保ち、これらの依存関係を維持するために必要な労力を最小限に抑えるには、依存関係の数をできるだけ少なくすることが重要です。 あなたがソフトウェアを維持しているとき、あなたの将来の自己はそれをあなたに感謝するでしょう。

ヒントImport Cost VSCode拡張機能は、インポートされたパッケージのサイズを自動的に表示します。

Next.jsのバージョンについていく

Next.jsとReactについていくことが重要です。 新しい機能にアクセスできるだけでなく、新しいバージョンにはバグ修正と潜在的なセキュリティ問題の修正も含まれます。 幸い、Next.jsは、Codemods(https://nextjs.org/docs/advanced-features/codemods)を提供することにより、アップグレードを非常に簡単にします。これらは、コードを自動的に更新する自動コード変換です。

依存関係を更新する

同じ理由で、Next.jsとReactのバージョンを実際に保つことが重要です。 他の依存関係を更新することも重要です。 Githubのdependabot(https://github.com/dependabot)は、ここで本当に役立ちます。 依存関係が更新されたプルリクエストが自動的に作成されます。 ただし、依存関係を更新すると問題が発生する可能性があるため、ここで自動化されたエンドツーエンドのテストを行うことは、本当に命の恩人になる可能性があります。

教訓:ログ集約サービスを使用する

アプリが適切に動作していることを確認し、問題を先制的に見つけるには、ログ集約サービスを構成することが絶対に必要であることがわかりました。 Vercelを使用すると、ログインしてログを表示できますが、これらはリアルタイムでストリーミングされ、永続化されません。 また、アラートと通知の構成もサポートしていません。

一部の例外は、表示されるまでに長い時間がかかる場合があります。 たとえば、特定のページに対してStale-While-Revalidateを構成しました。 ある時点で、ページが更新されておらず、古いデータが提供されていることに気づきました。 Vercelのログを確認したところ、ページのバックグラウンドレンダリング中に例外が発生していることがわかりました。 ログ集約サービスを使用し、例外のアラートを構成することで、これをはるかに早く発見できたはずです。

ログ集約サービスは、Vercelの料金プランの制限を監視するのにも役立ちます。 Vercelの使用状況ページでも、これに関する洞察が得られますが、ログ集約サービスを使用すると、特定のしきい値に達したときに通知を追加できます。 特に請求に関しては、予防は治療よりも優れています。

Vercelは、Datadog、Logtail、Logalert、Sentryなどを備えた、ログ集約サービスとのすぐに使用できる統合を多数提供します。

DatadogでのNext.jsリクエストログの表示
DatadogでのNext.jsリクエストログの表示(大プレビュー)

参考文献

  • 「統合」、Vercel

教訓:Next.jsの書き換え機能により、段階的な採用が可能

現在のウェブサイトに深刻な問題がない限り、多くの顧客がウェブサイト全体を書き直すことに興奮することはないでしょう。 しかし、Web Vitalsの観点から最も重要なページのみを再構築することから始めることができたらどうでしょうか? それはまさに私たちが別の顧客のためにしたことです。 サイト全体を再構築するのではなく、SEOと変換にとって最も重要なページのみを再構築します。 この場合、製品の詳細とカテゴリのページです。 Next.jsでそれらを再構築することにより、パフォーマンスが大幅に向上しました。

Next.jsの書き換え機能はこれに最適です。 カタログページを含む新しいNext.jsフロントエンドを構築し、それをCDNにデプロイしました。 他のすべての既存のページは、Next.jsによって既存のWebサイトに書き換えられます。 このようにして、Next.jsサイトのメリットを低労力または低リスクで開始できます。

参考文献

  • 「書き換え」、Next.jsドキュメント

次は何ですか?

プロジェクトの最初のバージョンをリリースし、本格的なパフォーマンステストを開始したとき、私たちはその結果に興奮しました。 ページの応答時間とWebVitalsが以前よりも大幅に向上しただけでなく、運用コストも以前の数分の1でした。 Next.jsとJAMStackを使用すると、通常、最も費用効果の高い方法でスケールアウトできます。

よりバックエンド指向のアーキテクチャからNext.jsのようなアーキテクチャに切り替えることは大きな一歩です。 学習曲線は非常に急である可能性があり、最初は、一部のチームメンバーは実際に自分の快適ゾーンの外にいると感じました。 私たちが行った小さな調整、この記事から学んだ教訓は、これを本当に助けました。 また、Next.jsでの開発経験により、生産性が大幅に向上します。 開発者のフィードバックサイクルは信じられないほど短いです!

参考文献

  • 「本番環境に移行」、Next.js Docs