コンテンツにスキップ
Pages Routerガイドセルフホスティング

Next.js アプリケーションをセルフホストする方法

Next.js アプリをデプロイする際、インフラストラクチャに基づいてさまざまな機能の処理方法を設定したくなる場合があります。

🎥 ウォッチ: Next.js のセルフホスティングについてさらに詳しく → YouTube (45分)

画像最適化

Image Optimizationnext/image を使用しており、next start を使用してデプロイする場合、設定なしでセルフホストで動作します。画像を最適化するための別のサービスを利用したい場合は、画像ローダーを設定できます。

Image Optimization は、next.config.js でカスタム画像ローダーを定義することで、静的エクスポートで使用できます。画像はビルド時ではなく、実行時に最適化されることに注意してください。

知っておくと良いこと

  • glibc ベースの Linux システムでは、Image Optimization は、過剰なメモリ使用量を防ぐために追加の設定が必要になる場合があります。
  • 最適化された画像のキャッシュ動作と TTL の設定方法についてさらに詳しく学んでください。
  • または、画像を自分で別に最適化している場合など、next/image のその他の利点を維持したまま、Image Optimization を無効にすることもできます。

プロキシ

Proxy は、next start を使用してデプロイする場合、設定なしでセルフホストで動作します。受信リクエストへのアクセスが必要なため、静的エクスポートではサポートされていません。

Proxy は、低レイテンシを確保するために、アプリケーションのすべてのルートまたはアセットの前に配置される可能性がある Edge Runtime を使用します。そうしたくない場合は、完全な Node.js Runtime を使用して Proxy を実行できます。

すべての Node.js API を必要とするロジックを追加したい場合(または外部パッケージを使用したい場合)、そのロジックを LayoutServer Component に移動できる場合があります。例えば、ヘッダーをチェックしてリダイレクトするなどです。ヘッダー、Cookie、またはクエリパラメータを使用して、next.config.js を介してリダイレクトまたは書き換えすることもできます。それでもうまくいかない場合は、カスタムサーバーを使用することもできます。

環境変数

Next.js は、ビルド時と実行時の両方の環境変数をサポートできます。

デフォルトでは、環境変数はサーバーでのみ利用可能です。ブラウザに環境変数を公開するには、NEXT_PUBLIC_ でプレフィックスを付ける必要があります。ただし、これらの公開環境変数は、next build 中に JavaScript バンドルにインライン化されます。

実行時環境変数を読み取るには、getServerSideProps を使用するか、App Router を段階的に採用することをお勧めします。

これにより、複数の環境を異なる値で昇格できる単一の Docker イメージを使用できます。

知っておくと良いこと

  • register 関数を使用して、サーバー起動時にコードを実行できます。

キャッシングと ISR

Next.js は、レスポンス、生成された静的ページ、ビルド出力、および画像、フォント、スクリプトなどのその他の静的アセットをキャッシュできます。

ページ(Incremental Static Regeneration を使用)のキャッシングと再検証では、同じ共有キャッシュが使用されます。デフォルトでは、このキャッシュは Next.js サーバーのファイルシステム(ディスク)に保存されます。Pages Router と App Router の両方を使用してセルフホスティングする場合、これは自動的に機能します

キャッシュされたページやデータを耐久性のあるストレージに永続化したい場合、または Next.js アプリケーションの複数のコンテナやインスタンス間でキャッシュを共有したい場合に、Next.js のキャッシュ設定を構成できます。

自動キャッシング

  • Next.js は、完全に不変なアセットに対して Cache-Control ヘッダーを public, max-age=31536000, immutable に設定します。これは上書きできません。これらの不変ファイルには、ファイル名に SHA ハッシュが含まれているため、安全に無期限にキャッシュできます。例: ローカル画像のインポート。画像の TTL は設定できます。
  • Incremental Static Regeneration (ISR) は、Cache-Control ヘッダーを s-maxage: <getStaticProps で再検証する時間>, stale-while-revalidate に設定します。この再検証時間は、getStaticProps 関数で秒単位で定義されます。revalidate: false を設定すると、デフォルトで 1 年間のキャッシュ期間になります。
  • 動的にレンダリングされるページは、ユーザー固有のデータがキャッシュされるのを防ぐために、Cache-Control ヘッダーを private, no-cache, no-store, max-age=0, must-revalidate に設定します。これは App Router と Pages Router の両方に適用されます。これにはDraft Modeも含まれます。

静的アセット

静的アセットを別のドメインまたは CDN でホストしたい場合は、next.config.jsassetPrefix 設定を使用できます。Next.js は JavaScript または CSS ファイルを取得する際にこのアセットプレフィックスを使用します。アセットを別のドメインに分離すると、DNS や TLS の解決に余分な時間がかかるという欠点があります。

assetPrefix についてさらに詳しく.

キャッシングの設定

デフォルトでは、生成されたキャッシュアセットはメモリ(デフォルト 50MB)とディスクに保存されます。Kubernetes のようなコンテナオーケストレーションプラットフォームで Next.js をホスティングしている場合、各 Pod はキャッシュのコピーを持ちます。デフォルトではキャッシュは Pod 間で共有されないため、古いデータが表示されるのを防ぐには、Next.js キャッシュを設定してキャッシュハンドラーを提供し、インメモリキャッシングを無効にすることができます。

セルフホスティング時に ISR/Data Cache の場所を設定するには、next.config.js ファイルでカスタムハンドラーを設定できます。

next.config.js
module.exports = {
  cacheHandler: require.resolve('./cache-handler.js'),
  cacheMaxMemorySize: 0, // disable default in-memory caching
}

次に、プロジェクトのルートに cache-handler.js を作成します。例:

cache-handler.js
const cache = new Map()
 
module.exports = class CacheHandler {
  constructor(options) {
    this.options = options
  }
 
  async get(key) {
    // This could be stored anywhere, like durable storage
    return cache.get(key)
  }
 
  async set(key, data, ctx) {
    // This could be stored anywhere, like durable storage
    cache.set(key, {
      value: data,
      lastModified: Date.now(),
      tags: ctx.tags,
    })
  }
 
  async revalidateTag(tags) {
    // tags is either a string or an array of strings
    tags = [tags].flat()
    // Iterate over all entries in the cache
    for (let [key, value] of cache) {
      // If the value's tags include the specified tag, delete this entry
      if (value.tags.some((tag) => tags.includes(tag))) {
        cache.delete(key)
      }
    }
  }
 
  // If you want to have temporary in memory cache for a single request that is reset
  // before the next request you can leverage this method
  resetRequestCache() {}
}

カスタムキャッシュハンドラーを使用すると、Next.js アプリケーションをホストするすべての Pod 間で一貫性を確保できます。例えば、キャッシュされた値を Redis や AWS S3 など、どこにでも保存できます。

知っておくと良いこと

  • revalidatePath は、キャッシュタグの便利なレイヤーです。revalidatePath を呼び出すと、指定されたページの特別なデフォルトタグで revalidateTag 関数が呼び出されます。

ビルドキャッシュ

Next.js は next build 中に ID を生成し、提供しているアプリケーションのバージョンを識別します。同じビルドを使用して複数のコンテナを起動する必要があります。

各環境ステージで再ビルドする場合、コンテナ間で一貫したビルド ID を生成する必要があります。next.config.jsgenerateBuildId コマンドを使用します。

next.config.js
module.exports = {
  generateBuildId: async () => {
    // This could be anything, using the latest git hash
    return process.env.GIT_HASH
  },
}

バージョン間のずれ

Next.js は、バージョン間のずれ のほとんどのインスタンスを自動的に軽減し、検出時に新しいアセットを取得するためにアプリケーションを自動的にリロードします。例えば、deploymentId に不一致がある場合、ページ間の遷移ではプリフェッチされた値を使用するのではなく、ハードナビゲーションを実行します。

アプリケーションがリロードされると、ページ遷移間で持続するように設計されていない場合、アプリケーションの状態が失われる可能性があります。例えば、URL の状態やローカルストレージを使用すると、ページリフレッシュ後も状態は持続します。ただし、useState のようなコンポーネントの状態は、そのような遷移では失われます。

手動での安全なシャットダウン

セルフホスティングする場合、SIGTERM または SIGINT シグナルでサーバーがシャットダウンする際にコードを実行したい場合があります。

環境変数 NEXT_MANUAL_SIG_HANDLEtrue に設定し、_document.js ファイル内にそのシグナル用のハンドラを登録できます。環境変数は .env ファイルではなく、package.json スクリプトに直接登録する必要があります。

注意: 手動シグナル処理は next dev では利用できません。

package.json
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "NEXT_MANUAL_SIG_HANDLE=true next start"
  }
}
pages/_document.js
if (process.env.NEXT_MANUAL_SIG_HANDLE) {
  process.on('SIGTERM', () => {
    console.log('Received SIGTERM: cleaning up')
    process.exit(0)
  })
  process.on('SIGINT', () => {
    console.log('Received SIGINT: cleaning up')
    process.exit(0)
  })
}