Next.jsアプリケーションのセルフホスティング方法
Next.jsアプリをデプロイする際、インフラに基づいてさまざまな機能の扱いを設定したい場合があります。
🎥 動画で学ぶ: Next.jsのセルフホスティングについて詳しく知る → YouTube (45分).
画像最適化
next/image
による画像最適化は、next start
を使用してデプロイする場合、設定なしでセルフホスティング環境で動作します。画像最適化を別サービスで行いたい場合は、画像ローダーを設定できます。
静的エクスポートでも、next.config.js
でカスタム画像ローダーを定義することで画像最適化を使用できます。画像はビルド時ではなく実行時に最適化される点に注意してください。
知っておくと便利:
ミドルウェア
ミドルウェアは、next start
を使用してデプロイする場合、設定なしでセルフホスティング環境で動作します。着信リクエストへのアクセスが必要なため、静的エクスポートではサポートされていません。
ミドルウェアはEdgeランタイムを使用し、アプリケーションのすべてのルートやアセットの前で実行されるため低遅延を確保します。これが不要な場合は、完全なNode.jsランタイムを使用してミドルウェアを実行できます。
すべてのNode.js APIを必要とするロジック(または外部パッケージ)を追加したい場合、このロジックをレイアウト内のサーバーコンポーネントに移動できる可能性があります。例えば、ヘッダーのチェックやリダイレクトなどです。また、next.config.js
でヘッダー、クッキー、クエリパラメータを使用してリダイレクトや書き換えも可能です。それでもうまくいかない場合は、カスタムサーバーも使用できます。
環境変数
Next.jsはビルド時と実行時の両方の環境変数をサポートしています。
デフォルトでは、環境変数はサーバーでのみ利用可能です。ブラウザに環境変数を公開するには、NEXT_PUBLIC_
をプレフィックスとして付ける必要があります。ただし、これらの公開環境変数はnext build
時にJavaScriptバンドルにインライン化されます。
実行時環境変数を読み取るには、getServerSideProps
を使用するか、App Routerへの段階的な移行をおすすめします。
これにより、異なる環境で異なる値を使用しながら単一のDockerイメージをプロモーションできます。
知っておくと便利:
register
関数を使用してサーバー起動時にコードを実行できます。- runtimeConfigオプションはスタンドアロン出力モードで動作しないため、推奨しません。代わりにApp Routerへの段階的な移行をおすすめします。
キャッシュとISR
Next.jsはレスポンス、生成された静的ページ、ビルド出力、および画像、フォント、スクリプトなどの静的アセットをキャッシュできます。
キャッシュとページの再検証(増分的静的再生成(ISR)を使用)は同じ共有キャッシュを使用します。デフォルトでは、このキャッシュはNext.jsサーバーのファイルシステム(ディスク上)に保存されます。これはPages RouterとApp Routerの両方を使用してセルフホスティングする場合に自動的に動作します。
キャッシュされたページとデータを耐久性のあるストレージに永続化したり、Next.jsアプリケーションの複数のコンテナやインスタンス間でキャッシュを共有したりしたい場合は、Next.jsキャッシュの場所を設定できます。
自動キャッシュ
- Next.jsは真に不変なアセットに
public, max-age=31536000, immutable
のCache-Control
ヘッダーを設定します。これは上書きできません。これらの不変ファイルにはファイル名にSHAハッシュが含まれるため、無期限に安全にキャッシュできます。例えば、静的画像インポートなどです。画像のTTLを設定できます。 - 増分的静的再生成(ISR)は
s-maxage: <getStaticPropsでの再検証時間>, stale-while-revalidate
のCache-Control
ヘッダーを設定します。この再検証時間はgetStaticProps
関数で秒単位で定義されます。revalidate: false
を設定すると、デフォルトで1年間のキャッシュ期間が適用されます。 - 動的にレンダリングされたページは、ユーザー固有のデータがキャッシュされないように
private, no-cache, no-store, max-age=0, must-revalidate
のCache-Control
ヘッダーを設定します。これはApp RouterとPages Routerの両方に適用されます。ドラフトモードも含まれます。
静的アセット
静的アセットを別のドメインやCDNでホストしたい場合は、next.config.js
でassetPrefix
設定を使用できます。Next.jsはJavaScriptやCSSファイルを取得する際にこのアセットプレフィックスを使用します。アセットを別のドメインに分離すると、DNSとTLS解決に余分な時間がかかるという欠点があります。
キャッシュの設定
デフォルトでは、生成されたキャッシュアセットはメモリ(デフォルトで50MB)とディスクに保存されます。Kubernetesなどのコンテナオーケストレーションプラットフォームを使用してNext.jsをホストしている場合、各ポッドはキャッシュのコピーを持ちます。ポッド間でキャッシュが共有されないため、デフォルトでは古いデータが表示されるのを防ぐために、Next.jsキャッシュを設定してカスタムキャッシュハンドラーを提供し、メモリ内キャッシュを無効にできます。
セルフホスティング時にISR/データキャッシュの場所を設定するには、next.config.js
ファイルでカスタムハンドラーを設定します:
module.exports = {
cacheHandler: require.resolve('./cache-handler.js'),
cacheMaxMemorySize: 0, // デフォルトのメモリ内キャッシュを無効化
}
次に、プロジェクトのルートにcache-handler.js
を作成します。例:
const cache = new Map()
module.exports = class CacheHandler {
constructor(options) {
this.options = options
}
async get(key) {
// これは耐久性のあるストレージなど、どこにでも保存できます
return cache.get(key)
}
async set(key, data, ctx) {
// これは耐久性のあるストレージなど、どこにでも保存できます
cache.set(key, {
value: data,
lastModified: Date.now(),
tags: ctx.tags,
})
}
async revalidateTag(tags) {
// tagsは文字列または文字列の配列です
tags = [tags].flat()
// キャッシュ内のすべてのエントリを反復処理
for (let [key, value] of cache) {
// 値のタグに指定されたタグが含まれている場合、このエントリを削除
if (value.tags.some((tag) => tags.includes(tag))) {
cache.delete(key)
}
}
}
// 単一リクエスト用の一時的なメモリ内キャッシュが必要で、
// 次のリクエスト前にリセットしたい場合はこのメソッドを活用できます
resetRequestCache() {}
}
カスタムキャッシュハンドラーを使用すると、Next.jsアプリケーションをホストするすべてのポッド間で一貫性を確保できます。例えば、キャッシュされた値をRedisやAWS S3など、どこにでも保存できます。
知っておくと便利:
revalidatePath
はキャッシュタグの上に構築された便利なレイヤーです。revalidatePath
を呼び出すと、指定されたページの特別なデフォルトタグでrevalidateTag
関数が呼び出されます。
ビルドキャッシュ
Next.jsはnext build
中にアプリケーションのバージョンを識別するIDを生成します。同じビルドを複数のコンテナで使用して起動する必要があります。
環境の各ステージでリビルドする場合、コンテナ間で使用する一貫したビルドIDを生成する必要があります。next.config.js
でgenerateBuildId
コマンドを使用します:
module.exports = {
generateBuildId: async () => {
// これは何でも構いません。最新のgitハッシュを使用する例
return process.env.GIT_HASH
},
}
バージョンスキュー
Next.jsはバージョンスキューのほとんどのインスタンスを自動的に緩和し、検出時に新しいアセットを取得するためにアプリケーションを自動的にリロードします。例えば、deploymentId
に不一致がある場合、ページ間の遷移はプリフェッチされた値を使用するのではなく、ハードナビゲーションを実行します。
アプリケーションがリロードされると、ページナビゲーション間で状態が永続化されないように設計されている場合、アプリケーション状態が失われる可能性があります。例えば、URL状態やローカルストレージを使用すると、ページリフレッシュ後も状態が保持されます。しかし、useState
などのコンポーネント状態はそのようなナビゲーションで失われます。
手動の適切なシャットダウン
セルフホスティング時、SIGTERM
またはSIGINT
シグナルでサーバーがシャットダウンする際にコードを実行したい場合があります。
環境変数NEXT_MANUAL_SIG_HANDLE
をtrue
に設定し、_document.js
ファイル内でそのシグナルのハンドラーを登録できます。環境変数は.env
ファイルではなく、package.json
スクリプトに直接登録する必要があります。
知っておくと便利: 手動シグナル処理は
next dev
では利用できません。
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "NEXT_MANUAL_SIG_HANDLE=true next start"
}
}
if (process.env.NEXT_MANUAL_SIG_HANDLE) {
process.on('SIGTERM', () => {
console.log('SIGTERMを受信: クリーンアップ中')
process.exit(0)
})
process.on('SIGINT', () => {
console.log('SIGINTを受信: クリーンアップ中')
process.exit(0)
})
}