Capítulo 323 de 456

Self-Hosting

Core Idea

Guia de referência para rodar Next.js fora da Vercel: reverse proxy, otimização de imagens, cache/ISR, build cache multi-container, encryption key de Server Functions, deployment ID e version skew, e graceful shutdown.

Key Concepts

  • Reverse proxy (nginx): recomendado na frente do servidor Next.js para lidar com requests malformadas, rate limiting e payload limits.
  • Image Optimization self-hosted: funciona com zero config via next start; para static export precisa de loader customizado.
  • Proxy self-hosted: funciona com zero config via next start; não suportado em static export (precisa de acesso à request).
  • Runtime env vars: só disponíveis no servidor por padrão; NEXT_PUBLIC_ expõe ao browser e é inlined no build.
  • Cache Handler customizado: classe com get, set, revalidateTag, resetRequestCache, configurada via cacheHandler em next.config.js.
  • cacheMaxMemorySize: 0: desabilita cache em memória padrão (necessário ao usar cache handler distribuído).
  • generateBuildId: gera um build ID consistente entre containers (ex. a partir do hash do git).
  • NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: chave AES (16/24/32 bytes, base64) que deve ser igual entre todas as instâncias para Server Functions funcionarem entre elas.
  • deploymentId: habilita proteção de version skew; assets levam ?dpl=<id> e requests de navegação levam header x-deployment-id.
  • NEXT_MANUAL_SIG_HANDLE: env var que permite tratar manualmente SIGTERM/SIGINT no shutdown (registrado via package.json, não .env).

Code Examples

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].flat()
    for (let [key, value] of cache) {
      if (value.tags.some((tag) => tags.includes(tag))) cache.delete(key)
    }
  }
  resetRequestCache() {}
}
  • O que demonstra: contrato mínimo de um cache handler customizado para armazenamento durável/compartilhado entre pods.
module.exports = {
  deploymentId: process.env.DEPLOYMENT_VERSION,
}
  • O que demonstra: habilitar detecção de version skew em deploys rolling multi-instância.

Reference Tables

Cache-ControlQuando aplica
public, max-age=31536000, immutableAssets com hash no nome (imagens estáticas locais) — não pode ser sobrescrito
s-maxage: <revalidate>, stale-while-revalidatePáginas com ISR (getStaticProps)
private, no-cache, no-store, max-age=0, must-revalidatePáginas dinamicamente renderizadas (App e Pages Router), incluindo Draft Mode

Anti-patterns

  • Rodar múltiplas instâncias sem NEXT_SERVER_ACTIONS_ENCRYPTION_KEY fixa: gera erro "Failed to find Server Action" porque cada build tem chave própria.
  • Confiar no cache em memória/disco padrão em compute efêmero (serverless/containers): cache é per-instance e some; usar cache handler durável.
  • Rebuild por ambiente sem generateBuildId consistente: quebra a suposição de "mesmo build sobe múltiplos containers".

Key Takeaways

  1. Cache e ISR compartilham o mesmo cache de servidor Next.js; por padrão fica no disco local por instância.
  2. Para múltiplos pods/containers, é necessário cache handler customizado + cacheMaxMemorySize: 0.
  3. deploymentId resolve version skew forçando hard navigation quando cliente e servidor divergem de versão.
  4. Server Functions exigem chave de encriptação idêntica entre instâncias em deploy multi-server.
  5. Graceful shutdown manual só funciona com next start, não com next dev.

Connects To

  • Static Exports: alternativa sem servidor Node quando Proxy/ISR não são necessários.
  • Production checklist: reforça env vars e Content Security Policy antes de ir a produção.