プロダクション環境への移行
Next.jsアプリケーションをプロダクション環境に移行する前に、最適なユーザー体験を確保するための推奨事項をご紹介します。
一般的な推奨事項
- 可能な限りキャッシュを活用する
- データベースとバックエンドを同じリージョンにデプロイする
- JavaScriptのサイズを可能な限り最小化する
- 重いJavaScriptバンドルの読み込みを必要時まで遅延させる
- ロギングが適切に設定されていることを確認する
- エラーハンドリングが適切に設定されていることを確認する
- 404(ページが見つかりません)と500(エラー)ページを設定する
- パフォーマンス計測を実施する
- Lighthouseを実行してパフォーマンス、ベストプラクティス、アクセシビリティ、SEOを確認する。最適な結果を得るには、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のメトリクスにバンドルサイズがどのように影響するか、および以下のような削減戦略を理解する:
- インポートコストとサイズを表示するようにコードエディタを設定
- より小さい代替パッケージを探す
- コンポーネントと依存関係を動的に読み込む