Menu

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

Next.jsアプリをデプロイする際に、インフラストラクチャに基づいてさまざまな機能の処理方法を設定することができます。

🎥 動画を見る: Next.jsのセルフホスティングについて詳しく学ぶ → YouTube(45分)

画像最適化

next/imageを通じた画像最適化は、next startを使用してデプロイする際にゼロ設定でセルフホスト環境で動作します。画像を最適化するための別のサービスを用意したい場合は、画像ローダーを設定できます。

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

補足:

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

Proxy

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

ProxyはEdge runtimeを使用します。これはすべての利用可能なNode.js APIのサブセットで、アプリケーションの全ルートまたはアセットの前で実行される可能性があるため、低レイテンシーを保証するのに役立ちます。これが不要な場合は、完全なNode.js runtimeを使用してProxyを実行できます。

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

環境変数

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

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

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

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

補足:

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

キャッシング とISR

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

ページのキャッシング とリバリデーション(増分静的再生成を使用)は、同じ共有キャッシュを使用します。デフォルトでは、このキャッシュはNext.jsサーバー上の(ディスク上の)ファイルシステムに保存されます。これはセルフホスティング時に自動的に動作します。Pages RouterとApp Routerの両方を使用する場合です。

キャッシュされたページとデータを永続的なストレージに保持したり、Next.jsアプリケーションの複数のコンテナまたはインスタンス間でキャッシュを共有したりする場合は、Next.jsキャッシュの場所を設定できます。

自動キャッシング

  • Next.jsは、真に不変のアセットに対してCache-Controlヘッダーをpublic, max-age=31536000, immutableに設定します。これはオーバーライドできません。これらの不変ファイルには、ファイル名にSHAハッシュが含まれているため、無期限に安全にキャッシュできます。例えば、静的画像インポート画像のTTLを設定できます。
  • 増分静的再生成(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の両方に適用されます。これにはドラフトモードも含まれます。

静的アセット

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

assetPrefixについてさらに詳しく学ぶ

キャッシングの設定

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

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

next.config.js
module.exports = {
  cacheHandler: require.resolve('./cache-handler.js'),
  cacheMaxMemorySize: 0, // デフォルトのメモリ内キャッシングを無効化
}

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

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.jsgenerateBuildIdコマンドを使用してください:

next.config.js
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では利用できません。

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('SIGTERMを受け取りました:クリーンアップしています')
    process.exit(0)
  })
  process.on('SIGINT', () => {
    console.log('SIGINTを受け取りました:クリーンアップしています')
    process.exit(0)
  })
}