Comment auto-héberger votre application Next.js

Lors du déploiement de votre application Next.js, vous pouvez configurer la manière dont les différentes fonctionnalités sont gérées en fonction de votre infrastructure.

🎥 Regarder : En savoir plus sur l'auto-hébergement de Next.js → YouTube (45 minutes).

Optimisation des images

L'optimisation des images via next/image fonctionne en auto-hébergement sans configuration lors d'un déploiement avec next start. Si vous préférez utiliser un service distinct pour optimiser les images, vous pouvez configurer un chargeur d'images.

L'optimisation des images peut être utilisée avec un export statique en définissant un chargeur d'images personnalisé dans next.config.js. Notez que les images sont optimisées lors de l'exécution, pas pendant la construction.

Bon à savoir :

Middleware

Le middleware fonctionne en auto-hébergement sans configuration lors d'un déploiement avec next start. Comme il nécessite un accès à la requête entrante, il n'est pas pris en charge avec un export statique.

Le middleware utilise le runtime Edge, un sous-ensemble des API Node.js disponibles pour garantir une faible latence, puisqu'il peut s'exécuter devant chaque route ou ressource de votre application. Si vous ne souhaitez pas cela, vous pouvez utiliser le runtime Node.js complet pour exécuter le middleware.

Si vous souhaitez ajouter une logique (ou utiliser un package externe) nécessitant toutes les API Node.js, vous pouvez déplacer cette logique dans un layout en tant que composant serveur. Par exemple, vérifier les en-têtes et rediriger. Vous pouvez également utiliser des en-têtes, cookies ou paramètres de requête pour rediriger ou réécrire via next.config.js. Si cela ne fonctionne pas, vous pouvez aussi utiliser un serveur personnalisé.

Variables d'environnement

Next.js prend en charge les variables d'environnement au moment de la construction et lors de l'exécution.

Par défaut, les variables d'environnement ne sont disponibles que sur le serveur. Pour exposer une variable d'environnement au navigateur, elle doit être préfixée par NEXT_PUBLIC_. Cependant, ces variables publiques seront intégrées dans le bundle JavaScript lors de next build.

Pour lire les variables d'environnement lors de l'exécution, nous recommandons d'utiliser getServerSideProps ou d'adopter progressivement le routeur App.

Cela vous permet d'utiliser une seule image Docker pouvant être promue à travers plusieurs environnements avec des valeurs différentes.

Bon à savoir :

  • Vous pouvez exécuter du code au démarrage du serveur avec la fonction register.
  • Nous ne recommandons pas d'utiliser l'option runtimeConfig, car elle ne fonctionne pas avec le mode de sortie autonome. Nous recommandons plutôt d'adopter progressivement le routeur App.

Mise en cache et ISR

Next.js peut mettre en cache les réponses, les pages statiques générées, les sorties de construction et d'autres ressources statiques comme les images, polices et scripts.

La mise en cache et la revalidation des pages (avec la régénération statique incrémentielle) utilisent le même cache partagé. Par défaut, ce cache est stocké sur le système de fichiers (sur disque) sur votre serveur Next.js. Cela fonctionne automatiquement en auto-hébergement avec les routeurs App et Pages.

Vous pouvez configurer l'emplacement du cache Next.js si vous souhaitez persister les pages et données mises en cache dans un stockage durable, ou partager le cache entre plusieurs conteneurs ou instances de votre application Next.js.

Mise en cache automatique

  • Next.js définit l'en-tête Cache-Control à public, max-age=31536000, immutable pour les ressources réellement immuables. Cela ne peut pas être modifié. Ces fichiers immuables contiennent un hachage SHA dans leur nom, ils peuvent donc être mis en cache indéfiniment. Par exemple, les imports d'images statiques. Vous pouvez configurer la durée de vie (TTL) pour les images.
  • La régénération statique incrémentielle (ISR) définit l'en-tête Cache-Control à s-maxage: <revalidate in getStaticProps>, stale-while-revalidate. Ce temps de revalidation est défini dans votre fonction getStaticProps en secondes. Si vous définissez revalidate: false, la durée de mise en cache par défaut sera d'un an.
  • Les pages rendues dynamiquement définissent un en-tête Cache-Control à private, no-cache, no-store, max-age=0, must-revalidate pour empêcher la mise en cache de données spécifiques à l'utilisateur. Cela s'applique aux routeurs App et Pages. Cela inclut également le mode brouillon.

Ressources statiques

Si vous souhaitez héberger des ressources statiques sur un domaine ou CDN différent, vous pouvez utiliser la configuration assetPrefix dans next.config.js. Next.js utilisera ce préfixe pour récupérer les fichiers JavaScript ou CSS. Séparer vos ressources sur un domaine différent a l'inconvénient d'un temps supplémentaire pour la résolution DNS et TLS.

En savoir plus sur assetPrefix.

Configuration de la mise en cache

Par défaut, les ressources mises en cache seront stockées en mémoire (50 Mo par défaut) et sur disque. Si vous hébergez Next.js avec une plateforme d'orchestration de conteneurs comme Kubernetes, chaque pod aura une copie du cache. Pour éviter d'afficher des données obsolètes puisque le cache n'est pas partagé entre les pods par défaut, vous pouvez configurer le cache Next.js pour fournir un gestionnaire de cache et désactiver la mise en cache en mémoire.

Pour configurer l'emplacement du cache ISR/Data lors de l'auto-hébergement, vous pouvez définir un gestionnaire personnalisé dans votre fichier next.config.js :

next.config.js
module.exports = {
  cacheHandler: require.resolve('./cache-handler.js'),
  cacheMaxMemorySize: 0, // désactiver la mise en cache en mémoire par défaut
}

Ensuite, créez cache-handler.js à la racine de votre projet, par exemple :

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

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

  async get(key) {
    // Cela peut être stocké n'importe où, comme un stockage durable
    return cache.get(key)
  }

  async set(key, data, ctx) {
    // Cela peut être stocké n'importe où, comme un stockage durable
    cache.set(key, {
      value: data,
      lastModified: Date.now(),
      tags: ctx.tags,
    })
  }

  async revalidateTag(tags) {
    // tags est soit une chaîne, soit un tableau de chaînes
    tags = [tags].flat()
    // Parcourir toutes les entrées du cache
    for (let [key, value] of cache) {
      // Si les tags de la valeur incluent le tag spécifié, supprimer cette entrée
      if (value.tags.some((tag) => tags.includes(tag))) {
        cache.delete(key)
      }
    }
  }

  // Si vous souhaitez avoir un cache temporaire en mémoire pour une seule requête qui est réinitialisé
  // avant la prochaine requête, vous pouvez utiliser cette méthode
  resetRequestCache() {}
}

L'utilisation d'un gestionnaire de cache personnalisé vous permet d'assurer la cohérence entre tous les pods hébergeant votre application Next.js. Par exemple, vous pouvez enregistrer les valeurs mises en cache n'importe où, comme Redis ou AWS S3.

Bon à savoir :

  • revalidatePath est une couche de commodité au-dessus des tags de cache. Appeler revalidatePath appellera la fonction revalidateTag avec un tag spécial par défaut pour la page fournie.

Cache de construction

Next.js génère un ID lors de next build pour identifier quelle version de votre application est servie. La même construction doit être utilisée et démarrée sur plusieurs conteneurs.

Si vous reconstruisez pour chaque étape de votre environnement, vous devrez générer un ID de construction cohérent à utiliser entre les conteneurs. Utilisez la commande generateBuildId dans next.config.js :

next.config.js
module.exports = {
  generateBuildId: async () => {
    // Cela peut être n'importe quoi, comme le dernier hash git
    return process.env.GIT_HASH
  },
}

Désynchronisation de version

Next.js atténue automatiquement la plupart des cas de désynchronisation de version et recharge automatiquement l'application pour récupérer les nouvelles ressources lorsqu'elle est détectée. Par exemple, s'il y a une incompatibilité dans le deploymentId, les transitions entre pages effectueront une navigation complète plutôt que d'utiliser une valeur préchargée.

Lors du rechargement de l'application, il peut y avoir une perte d'état si elle n'est pas conçue pour persister entre les navigations. Par exemple, utiliser l'état de l'URL ou le stockage local persisterait l'état après un rafraîchissement. Cependant, l'état des composants comme useState serait perdu lors de telles navigations.

Arrêts gracieux manuels

En auto-hébergement, vous pouvez exécuter du code lorsque le serveur s'arrête sur les signaux SIGTERM ou SIGINT.

Vous pouvez définir la variable d'environnement NEXT_MANUAL_SIG_HANDLE à true puis enregistrer un gestionnaire pour ce signal dans votre fichier _document.js. Vous devrez enregistrer la variable d'environnement directement dans le script package.json, et non dans le fichier .env.

Bon à savoir : La gestion manuelle des signaux n'est pas disponible dans 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('Reçu SIGTERM : nettoyage')
    process.exit(0)
  })
  process.on('SIGINT', () => {
    console.log('Reçu SIGINT : nettoyage')
    process.exit(0)
  })
}

On this page