Capítulo 78 de 456

Self-Hosting

Core Idea

Guia de referência para rodar Next.js fora da Vercel (Node.js server, Docker, next start), cobrindo reverse proxy, cache, variáveis de ambiente, multi-instância e streaming.

Key Concepts

  • Reverse proxy: recomenda-se nginx (ou similar) na frente do Next.js pra tratar requisições malformadas, rate limiting e payload limits, liberando o servidor Next.js pra renderizar.
  • NEXT_PUBLIC_ prefix: única forma de expor env var ao browser; é inlined no JS bundle durante next build. Sem o prefixo, a env var só existe no servidor.
  • connection(): importado de next/server, força dynamic rendering e faz process.env.MY_VALUE ser avaliado em runtime (não no build), permitindo uma única imagem Docker promovida entre ambientes com valores diferentes.
  • Cache handler customizado: cacheHandler em next.config.js + cacheMaxMemorySize: 0 permite substituir o cache padrão (disco local por instância) por armazenamento durável (Redis, S3) compartilhado entre pods/containers.
  • generateBuildId: gera um build ID consistente entre containers quando se rebuilda por estágio de ambiente (ex. usando git hash).
  • NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: chave AES (16/24/32 bytes, base64) que deve ser igual entre todas as instâncias multi-servidor, senão Server Functions falham com "Failed to find Server Action".
  • deploymentId: habilita proteção contra version skew em rolling deployments — assets estáticos ganham ?dpl=<id>, navegação client-side envia header x-deployment-id; mismatch dispara hard navigation.
  • refreshTags(): método do cache handler custom chamado antes de cada requisição pra sincronizar invalidação de tags entre instâncias (sem isso, revalidateTag() só invalida a instância que chamou).
  • X-Accel-Buffering: no: header necessário pra desabilitar buffering no nginx e permitir streaming (essencial para PPR funcionar com vantagem de time-to-first-byte).

Code Examples

module.exports = {
  cacheHandler: require.resolve('./cache-handler.js'),
  cacheMaxMemorySize: 0, // disable default in-memory caching
}
  • O que demonstra: apontar pra um cache handler customizado e desligar o cache em memória padrão.
module.exports = class CacheHandler {
  constructor(options) { this.options = options }
  async get(key) { /* durable storage */ }
  async set(key, data, ctx) { /* durable storage, ctx.tags */ }
  async revalidateTag(tags) { /* invalida entradas com a tag */ }
  resetRequestCache() {}
}
  • O que demonstra: shape mínimo de um cache handler custom (get/set/revalidateTag/resetRequestCache).
module.exports = {
  async headers() {
    return [
      { source: '/:path*{/}?', headers: [{ key: 'X-Accel-Buffering', value: 'no' }] },
    ]
  },
}
  • O que demonstra: desabilitar buffering do nginx para habilitar streaming corretamente.

Reference Tables

Cache-Control headerQuando
public, max-age=31536000, immutableAssets imutáveis (hash no nome), não pode ser sobrescrito
s-maxage: <revalidate>, stale-while-revalidatePáginas com ISR
private, no-cache, no-store, max-age=0, must-revalidatePáginas dinâmicas (dado específico do usuário), inclui Draft Mode

Anti-patterns

  • Múltiplas instâncias sem deploymentId/chave de encriptação consistente/cache compartilhado: causa "Failed to find Server Action", assets ausentes e navegação inconsistente entre pods.
  • Rodar atrás de load balancer/proxy sem suporte a chunked transfer: elimina o ganho de time-to-first-byte do PPR, entregando shell e conteúdo dinâmico juntos só após o render completo.

Key Takeaways

  1. Em deployments multi-instância, três coisas precisam ser consistentes entre pods: chave de encriptação de Server Actions, deploymentId e cache (via handler custom com refreshTags()).
  2. next/image, Proxy e Cache Components funcionam self-hosted com zero config em next start; Proxy não funciona com static export.
  3. Toda a cadeia de infraestrutura (load balancer, reverse proxy) precisa suportar streaming/chunked encoding para PPR funcionar corretamente.
  4. after() é totalmente suportado self-hosted, mas requer um drain period de 10-30s no shutdown (SIGINT/SIGTERM) pra callbacks pendentes terminarem.

Connects To

  • Cache Components (use cache): cache handler custom é a peça de infra que sustenta cache em produção multi-instância.
  • How Revalidation Works: arquitetura de tags referenciada para refreshTags().
  • Streaming (ch083): pré-requisito de infraestrutura discutido aqui em detalhe operacional.