プロダクション環境への移行

Next.jsアプリケーションをプロダクション環境に移行する前に、最適なユーザー体験を確保するための推奨事項をご紹介します。

一般的な推奨事項

キャッシュ

キャッシュを活用することで応答時間を改善し、外部サービスへのリクエスト数を削減できます。Next.jsは自動的に/_next/staticから配信されるJavaScript、CSS、静的画像、その他のメディアに対して不変のキャッシュヘッダーを追加します。

Cache-Control: public, max-age=31536000, immutable

next.config.jsで設定したCache-Controlヘッダーは、静的アセットが効果的にキャッシュされるようにプロダクション環境で上書きされます。静的生成されたページのキャッシュを再検証する必要がある場合は、ページのgetStaticProps関数でrevalidateを設定できます。next/imageを使用している場合、デフォルトの画像最適化ローダーに対してminimumCacheTTLを設定できます。

豆知識: next devでアプリケーションをローカルで実行する場合、キャッシュを防ぐためにヘッダーが上書きされます。

Cache-Control: no-cache, no-store, max-age=0, must-revalidate

動的なレスポンスに対してgetServerSidePropsやAPIルート内でキャッシュヘッダーを使用することもできます。例えば、stale-while-revalidateを使用します。

// この値は10秒間新鮮とみなされます(s-maxage=10)。
// 次の10秒以内にリクエストが繰り返されると、以前にキャッシュされた値がまだ新鮮な状態で使用されます。
// 59秒以内にリクエストが繰り返されると、キャッシュされた値は古くなりますが、まだ表示されます(stale-while-revalidate=59)。
//
// バックグラウンドで、キャッシュを新しい値で更新するための再検証リクエストが行われます。
// ページを更新すると、新しい値が表示されます。
export async function getServerSideProps({ req, res }) {
  res.setHeader(
    'Cache-Control',
    'public, s-maxage=10, stale-while-revalidate=59'
  )

  return {
    props: {},
  }
}

デフォルトでは、データの取得方法に応じてCache-Controlヘッダーが異なる方法で設定されます。

  • ページがgetServerSidePropsまたはgetInitialPropsを使用している場合、キャッシュできないレスポンスの誤ったキャッシュを防ぐために、next startで設定されたデフォルトのCache-Controlヘッダーが使用されます。getServerSidePropsを使用しながら異なるキャッシュ動作を希望する場合は、上記のように関数内でres.setHeader('Cache-Control', '希望する値')を使用します。
  • ページがgetStaticPropsを使用している場合、revalidateが使用されていればs-maxage=REVALIDATE_SECONDS, stale-while-revalidate、使用されていなければs-maxage=31536000, stale-while-revalidateというCache-Controlヘッダーが設定され、可能な限り長期間キャッシュされます。

豆知識: 動的なレスポンスのキャッシュをサポートするには、デプロイプロバイダーがキャッシュをサポートしている必要があります。セルフホスティングの場合、Redisのようなキーバリューストアを使用してこのロジックを自分で追加する必要があります。Vercelを使用している場合、設定なしでエッジキャッシュが機能します

JavaScriptサイズの削減

ブラウザに送信されるJavaScriptの量を削減するために、各JavaScriptバンドルに含まれる内容を理解する以下のツールを使用できます:

  • Import Cost - VSCode内でインポートされたパッケージのサイズを表示
  • Package Phobia - プロジェクトに新しいdev依存関係を追加するコストを調査
  • Bundle Phobia - 依存関係がバンドルサイズに与える影響を分析
  • Webpack Bundle Analyzer - Webpackの出力ファイルサイズをインタラクティブなズーム可能なツリーマップで可視化
  • bundlejs - プロジェクトを迅速にバンドル&ミニファイし、圧縮されたgzip/brotliバンドルサイズを表示するオンラインツール

next build時に、pages/ディレクトリ内の各ファイルは自動的に独自のJavaScriptバンドルにコード分割されます。動的インポートを使用して、コンポーネントやライブラリの遅延読み込みも可能です。例えば、モーダルコードの読み込みをユーザーが開くボタンをクリックするまで遅延させることができます。

ロギング

Next.jsはクライアントとサーバーの両方で実行されるため、複数の形式のロギングがサポートされています:

  • ブラウザでのconsole.log
  • サーバーでのstdout

構造化ロギングパッケージが必要な場合は、Pinoをお勧めします。Vercelを使用している場合、Next.jsと互換性のある事前構築済みのロギング統合があります。

エラーハンドリング

未処理の例外が発生した場合、500ページを使用してユーザー体験を制御できます。Next.jsのデフォルトテーマではなく、ブランドに合わせてカスタマイズすることをお勧めします。

Sentryのようなツールを使用して例外を記録・追跡することもできます。この例では、Next.js用のSentry SDKを使用してクライアント側とサーバー側の両方でエラーをキャッチ・報告する方法を示しています。また、Vercel向けのSentry統合もあります。

読み込みパフォーマンス

読み込みパフォーマンスを改善するには、まず何を測定し、どのように測定するかを決定する必要があります。Core Web Vitalsは、自身のWebブラウザを使用して測定される優れた業界標準です。Core Web Vitalsのメトリクスに慣れていない場合は、このブログ記事を確認し、読み込みパフォーマンスのドライバーとなる特定のメトリクスを決定してください。理想的には、以下の環境で読み込みパフォーマンスを測定したいでしょう:

  • ラボ環境: 自身のコンピュータまたはシミュレータを使用
  • フィールド環境: 実際の訪問者からの実世界のデータを使用
  • ローカル環境: デバイス上で実行されるテストを使用
  • リモート環境: クラウドで実行されるテストを使用

読み込みパフォーマンスを測定できるようになったら、以下の戦略を使用して反復的に改善を行います。1つの戦略を適用し、新しいパフォーマンスを測定し、改善が見られなくなるまで調整を続けます。その後、次の戦略に移行できます。

  • データベースやAPIがデプロイされているリージョンに近いキャッシュリージョンを使用する
  • キャッシュセクションで説明したように、バックエンドに過負荷をかけないstale-while-revalidate値を使用する
  • 増分的静的再生成を使用してバックエンドへのリクエスト数を削減する
  • 未使用のJavaScriptを削除する。このブログ記事を確認して、Core Web Vitalsのメトリクスにバンドルサイズがどのように影響するか、および以下のような削減戦略を理解する:
    • インポートコストとサイズを表示するようにコードエディタを設定
    • より小さい代替パッケージを探す
    • コンポーネントと依存関係を動的に読み込む