Wie Sie Ihre Next.js-Anwendung selbst hosten können

Beim Deployment Ihrer Next.js-App möchten Sie möglicherweise konfigurieren, wie verschiedene Funktionen basierend auf Ihrer Infrastruktur behandelt werden.

🎥 Video: Mehr über das Selbsthosting von Next.js erfahren → YouTube (45 Minuten).

Bildoptimierung

Die Bildoptimierung über next/image funktioniert beim Selbsthosting ohne zusätzliche Konfiguration, wenn Sie next start verwenden. Wenn Sie einen separaten Dienst zur Bildoptimierung bevorzugen, können Sie einen Image Loader konfigurieren.

Die Bildoptimierung kann mit einem statischen Export verwendet werden, indem Sie einen benutzerdefinierten Image Loader in next.config.js definieren. Beachten Sie, dass Bilder zur Laufzeit und nicht während des Builds optimiert werden.

Wissenswert:

Middleware

Middleware funktioniert beim Selbsthosting ohne zusätzliche Konfiguration, wenn Sie next start verwenden. Da sie Zugriff auf die eingehende Anfrage benötigt, wird sie bei Verwendung eines statischen Exports nicht unterstützt.

Middleware verwendet die Edge Runtime, eine Teilmenge aller verfügbaren Node.js-APIs, um eine geringe Latenz zu gewährleisten, da sie vor jeder Route oder jedem Asset Ihrer Anwendung ausgeführt werden kann. Wenn Sie dies nicht möchten, können Sie die vollständige Node.js-Runtime für die Middleware verwenden.

Wenn Sie Logik hinzufügen möchten (oder ein externes Paket verwenden), das alle Node.js-APIs erfordert, können Sie diese Logik möglicherweise in ein Layout als Server-Komponente verschieben. Zum Beispiel das Überprüfen von Headers und Weiterleitungen. Sie können auch Header, Cookies oder Abfrageparameter verwenden, um über next.config.js Weiterleitungen oder Rewrites durchzuführen. Falls das nicht funktioniert, können Sie auch einen benutzerdefinierten Server verwenden.

Umgebungsvariablen

Next.js unterstützt sowohl Build-Zeit- als auch Laufzeit-Umgebungsvariablen.

Standardmäßig sind Umgebungsvariablen nur auf dem Server verfügbar. Um eine Umgebungsvariable im Browser verfügbar zu machen, muss sie mit NEXT_PUBLIC_ präfixiert werden. Diese öffentlichen Umgebungsvariablen werden jedoch während next build in das JavaScript-Bundle eingebettet.

Um Laufzeit-Umgebungsvariablen zu lesen, empfehlen wir die Verwendung von getServerSideProps oder die schrittweise Migration zum App Router.

Dies ermöglicht die Verwendung eines einzelnen Docker-Images, das in mehreren Umgebungen mit unterschiedlichen Werten eingesetzt werden kann.

Wissenswert:

  • Sie können Code beim Serverstart mit der register-Funktion ausführen.
  • Wir empfehlen nicht die Verwendung der runtimeConfig-Option, da diese nicht mit dem standalone-Ausgabemodus funktioniert. Stattdessen empfehlen wir die schrittweise Migration zum App Router.

Caching und ISR

Next.js kann Antworten, generierte statische Seiten, Build-Ausgaben und andere statische Assets wie Bilder, Schriftarten und Skripte cachen.

Das Caching und Revalidieren von Seiten (mit Inkrementellem Statischen Regenerieren (ISR)) verwendet den gemeinsamen Cache. Standardmäßig wird dieser Cache auf dem Dateisystem (auf der Festplatte) Ihres Next.js-Servers gespeichert. Dies funktioniert automatisch beim Selbsthosting mit sowohl dem Pages- als auch dem App-Router.

Sie können den Next.js-Cache-Speicherort konfigurieren, wenn Sie zwischengespeicherte Seiten und Daten in einem dauerhaften Speicher persistieren oder den Cache über mehrere Container oder Instanzen Ihrer Next.js-Anwendung teilen möchten.

Automatisches Caching

  • Next.js setzt den Cache-Control-Header von public, max-age=31536000, immutable für wirklich unveränderliche Assets. Dies kann nicht überschrieben werden. Diese unveränderlichen Dateien enthalten einen SHA-Hash im Dateinamen und können daher sicher unbegrenzt zwischengespeichert werden. Zum Beispiel Statische Bildimporte. Sie können die TTL für Bilder konfigurieren.
  • Inkrementelles Statisches Regenerieren (ISR) setzt den Cache-Control-Header auf s-maxage: <revalidate in getStaticProps>, stale-while-revalidate. Diese Revalidierungszeit wird in Ihrer getStaticProps-Funktion in Sekunden definiert. Wenn Sie revalidate: false setzen, wird standardmäßig eine einjährige Cache-Dauer verwendet.
  • Dynamisch gerenderte Seiten setzen einen Cache-Control-Header von private, no-cache, no-store, max-age=0, must-revalidate, um das Caching benutzerspezifischer Daten zu verhindern. Dies gilt sowohl für den App- als auch für den Pages-Router. Dazu gehört auch der Draft Mode.

Statische Assets

Wenn Sie statische Assets auf einer anderen Domain oder einem CDN hosten möchten, können Sie die assetPrefix-Konfiguration in next.config.js verwenden. Next.js verwendet dieses Asset-Präfix beim Abrufen von JavaScript- oder CSS-Dateien. Die Trennung Ihrer Assets auf eine andere Domain hat jedoch den Nachteil einer zusätzlichen Zeit für DNS- und TLS-Auflösung.

Mehr über assetPrefix erfahren.

Caching konfigurieren

Standardmäßig werden zwischengespeicherte Assets im Speicher (standardmäßig 50 MB) und auf der Festplatte gespeichert. Wenn Sie Next.js mit einer Container-Orchestrierungsplattform wie Kubernetes hosten, hat jeder Pod eine Kopie des Caches. Um zu verhindern, dass veraltete Daten angezeigt werden, da der Cache standardmäßig nicht zwischen Pods geteilt wird, können Sie den Next.js-Cache so konfigurieren, dass er einen Cache-Handler bereitstellt und das In-Memory-Caching deaktiviert.

Um den ISR/Daten-Cache-Speicherort beim Selbsthosting zu konfigurieren, können Sie einen benutzerdefinierten Handler in Ihrer next.config.js-Datei konfigurieren:

next.config.js
module.exports = {
  cacheHandler: require.resolve('./cache-handler.js'),
  cacheMaxMemorySize: 0, // In-Memory-Caching deaktivieren
}

Erstellen Sie dann cache-handler.js im Stammverzeichnis Ihres Projekts, zum Beispiel:

cache-handler.js
const cache = new Map()

module.exports = class CacheHandler {
  constructor(options) {
    this.options = options
  }

  async get(key) {
    // Dies könnte überall gespeichert werden, z.B. in dauerhaftem Speicher
    return cache.get(key)
  }

  async set(key, data, ctx) {
    // Dies könnte überall gespeichert werden, z.B. in dauerhaftem Speicher
    cache.set(key, {
      value: data,
      lastModified: Date.now(),
      tags: ctx.tags,
    })
  }

  async revalidateTag(tags) {
    // tags ist entweder ein String oder ein Array von Strings
    tags = [tags].flat()
    // Durchlaufen Sie alle Einträge im Cache
    for (let [key, value] of cache) {
      // Wenn die Tags des Werts den angegebenen Tag enthalten, löschen Sie diesen Eintrag
      if (value.tags.some((tag) => tags.includes(tag))) {
        cache.delete(key)
      }
    }
  }

  // Wenn Sie einen temporären In-Memory-Cache für eine einzelne Anfrage haben möchten, der zurückgesetzt wird
  // bevor die nächste Anfrage kommt, können Sie diese Methode nutzen
  resetRequestCache() {}
}

Die Verwendung eines benutzerdefinierten Cache-Handlers ermöglicht es Ihnen, Konsistenz über alle Pods hinweg sicherzustellen, die Ihre Next.js-Anwendung hosten. Sie können die zwischengespeicherten Werte beispielsweise überall speichern, wie in Redis oder AWS S3.

Wissenswert:

  • revalidatePath ist eine bequeme Abstraktion über Cache-Tags. Der Aufruf von revalidatePath ruft die revalidateTag-Funktion mit einem speziellen Standard-Tag für die angegebene Seite auf.

Build-Cache

Next.js generiert während next build eine ID, um zu identifizieren, welche Version Ihrer Anwendung bereitgestellt wird. Der gleiche Build sollte verwendet und auf mehreren Containern gestartet werden.

Wenn Sie für jede Stufe Ihrer Umgebung neu bauen, müssen Sie eine konsistente Build-ID generieren, die zwischen Containern verwendet wird. Verwenden Sie den generateBuildId-Befehl in next.config.js:

next.config.js
module.exports = {
  generateBuildId: async () => {
    // Dies könnte alles sein, z.B. der neueste Git-Hash
    return process.env.GIT_HASH
  },
}

Versionsabweichung

Next.js mildert die meisten Fälle von Versionsabweichungen (Version Skew) automatisch und lädt die Anwendung automatisch neu, um neue Assets abzurufen, wenn eine Abweichung erkannt wird. Wenn beispielsweise eine Diskrepanz in der deploymentId vorliegt, führen Seitenübergänge eine Hard Navigation anstelle der Verwendung eines vorab abgerufenen Werts durch.

Wenn die Anwendung neu geladen wird, kann der Anwendungszustand verloren gehen, wenn er nicht für die Persistenz zwischen Seitenwechseln ausgelegt ist. Beispielsweise würde der Zustand in der URL oder im lokalen Speicher nach einem Seitenneuladen erhalten bleiben, während der Komponentenzustand wie useState bei solchen Navigationen verloren geht.

Manuelle Graceful Shutdowns

Beim Selbsthosting möchten Sie möglicherweise Code ausführen, wenn der Server mit SIGTERM oder SIGINT-Signalen heruntergefahren wird.

Sie können die Umgebungsvariable NEXT_MANUAL_SIG_HANDLE auf true setzen und dann einen Handler für dieses Signal in Ihrer _document.js-Datei registrieren. Sie müssen die Umgebungsvariable direkt im package.json-Skript registrieren und nicht in der .env-Datei.

Wissenswert: Die manuelle Signalbehandlung ist in next dev nicht verfügbar.

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)
  })
}

On this page