Next.jsアプリケーションのセルフホスティング方法
Next.jsアプリをデプロイする際、インフラストラクチャに基づいて異なる機能の取り扱い方を設定したい場合があります。
🎥 視聴: Next.jsのセルフホスティングについて詳しく学ぶ → YouTube (45分)。
画像最適化
next/image
を通じた画像最適化は、next start
を使用してデプロイする際に追加設定なしでセルフホストで動作します。画像を最適化するための別のサービスを使用したい場合は、画像ローダーを設定できます。
画像最適化は、next.config.js
でカスタム画像ローダーを定義することで静的エクスポートでも使用できます。画像の最適化はビルド時ではなく実行時に行われることに注意してください。
補足:
ミドルウェア
ミドルウェアは、next start
を使用してデプロイする際に追加設定なしでセルフホストで動作します。受信リクエストへのアクセスが必要なため、静的エクスポートを使用する場合はサポートされていません。
ミドルウェアはアプリケーションのすべてのルートやアセットの前で実行される可能性があるため、低レイテンシを確保するために利用可能なNode.js APIのサブセットであるランタイムを使用します。これを望まない場合は、完全なNode.jsランタイムを使用してミドルウェアを実行できます。
すべてのNode.js APIを必要とするロジックを追加(または外部パッケージを使用)したい場合は、このロジックをServer Componentとしてレイアウトに移動できる可能性があります。例えば、ヘッダーの確認やリダイレクトなどです。また、ヘッダー、Cookie、またはクエリパラメータを使用して、next.config.js
を通じてリダイレクトやリライトを行うこともできます。それでもうまくいかない場合は、カスタムサーバーを使用することもできます。
環境変数
Next.jsはビルド時と実行時の両方の環境変数をサポートしています。
デフォルトでは、環境変数はサーバーでのみ使用可能です。環境変数をブラウザに公開するには、NEXT_PUBLIC_
の接頭辞を付ける必要があります。ただし、これらの公開環境変数はnext build
時にJavaScriptバンドルにインライン化されます。
動的レンダリング中にサーバー上で環境変数を安全に読み取ることができます。
import { connection } from 'next/server'
export default async function Component() {
await connection()
// cookies、headersなどの動的APIも
// 動的レンダリングを適用するため、
// この環境変数は実行時に評価されます
const value = process.env.MY_VALUE
// ...
}
これにより、異なる値を持つ複数の環境間で単一のDockerイメージを使用できます。
補足:
register
関数を使用して、サーバー起動時にコードを実行できます。- スタンドアロン出力モードでは動作しないため、runtimeConfigオプションの使用はお勧めしません。代わりに、Appルーターを段階的に採用することをお勧めします。
キャッシュとISR
Next.jsはレスポンス、生成された静的ページ、ビルド出力、および画像、フォント、スクリプトなどの静的アセットをキャッシュできます。
ページのキャッシュと再検証(Incremental Static Regenerationを使用)は同じ共有キャッシュを使用します。デフォルトでは、このキャッシュはNext.jsサーバー上のファイルシステム(ディスク)に保存されます。これはPagesルーターとAppルーターの両方を使用したセルフホスティングで自動的に機能します。
キャッシュされたページやデータを永続的なストレージに保存したり、Next.jsアプリケーションの複数のコンテナやインスタンス間でキャッシュを共有したりしたい場合は、Next.jsキャッシュの場所を設定できます。
自動キャッシング
- Next.jsは真に不変なアセットに対して
Cache-Control
ヘッダーにpublic, max-age=31536000, immutable
を設定します。これはオーバーライドできません。これらの不変ファイルにはファイル名にSHAハッシュが含まれているため、無期限にキャッシュしても安全です。例えば、静的画像インポートなどです。画像のTTLを設定することもできます。 - Incremental Static Regeneration(ISR)は
Cache-Control
ヘッダーにs-maxage: <revalidate in getStaticProps>, stale-while-revalidate
を設定します。この再検証時間はgetStaticProps
関数で秒単位で定義されます。revalidate: false
を設定すると、デフォルトで1年間のキャッシュ期間になります。 - 動的にレンダリングされたページは、ユーザー固有のデータがキャッシュされないように
Cache-Control
ヘッダーにprivate, no-cache, no-store, max-age=0, must-revalidate
を設定します。これはAppルーターとPagesルーターの両方に適用されます。これにはドラフトモードも含まれます。
静的アセット
異なるドメインや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
などのコンポーネントの状態は、そのようなナビゲーションで失われます。
ストリーミングとSuspense
Next.js Appルーターはセルフホスティング時にストリーミングレスポンスをサポートしています。Nginxやそれに類似するプロキシを使用している場合は、ストリーミングを有効にするためにバッファリングを無効にするように設定する必要があります。
例えば、NginxではX-Accel-Buffering
をno
に設定してバッファリングを無効にできます:
module.exports = {
async headers() {
return [
{
source: '/:path*{/}?',
headers: [
{
key: 'X-Accel-Buffering',
value: 'no',
},
],
},
]
},
}
部分的なプリレンダリング
部分的なプリレンダリング(実験的)はNext.jsでデフォルトで動作し、CDN専用の機能ではありません。これには、(next start
を通じた)Node.jsサーバーとしてのデプロイや、Dockerコンテナと共に使用する場合も含まれます。
CDNとの使用
Next.jsアプリケーションの前にCDNを使用する場合、動的APIにアクセスすると、ページにはCache-Control: private
レスポンスヘッダーが含まれます。これにより、結果のHTMLページがキャッシュ不可としてマークされます。ページが完全に静的にプリレンダリングされている場合は、Cache-Control: public
が含まれ、ページがCDNにキャッシュされることを許可します。
静的なコンポーネントと動的なコンポーネントの両方が混在する必要がない場合は、ルート全体を静的にして、出力HTMLをCDNにキャッシュできます。この自動静的最適化は、動的APIが使用されていない場合にnext build
を実行したときのデフォルトの動作です。
部分的なプリレンダリングが安定版に移行するにつれて、デプロイメントアダプターAPIを通じてサポートを提供する予定です。
after
after
はnext start
でセルフホスティングする場合に完全にサポートされています。
サーバーを停止する際は、SIGINT
またはSIGTERM
シグナルを送信して待機することで、正常なシャットダウンを確保してください。これにより、Next.jsサーバーはafter
内で使用される保留中のコールバック関数またはプロミスが完了するまで待機できます。