Back

Guia de migração para o Next.js 16: Turbopack, proxy.ts, Cache Components e todas as breaking changes explicadas

Seu pipeline de CI acabou de quebrar depois de subir o next pra ^16.0.0. O log de erros tem 400 linhas, metade do seu middleware tá quebrado, e apareceu uma nova convenção proxy.ts que você nunca viu na vida. Bem-vindo ao upgrade do Next.js 16.

Next.js 16 é a release mais significativa em termos de arquitetura desde que o App Router chegou na v13. Webpack morreu — Turbopack é o único bundler. A convenção middleware.ts foi substituída por proxy.ts. O modelo de cache inteiro foi reconstruído em torno de Cache Components e da diretiva use cache. E se você tá rodando em containers, tem mudanças de comportamento de memória que vão te pegar em produção se você não souber delas.

Esse guia passa por cada breaking change, explica o porquê, dá os comandos exatos de codemod, e o caminho de migração manual pra quando os codemods não dão conta. Seja um site de marketing pequeno ou uma app enterprise de 200 rotas, esse documento é tudo que você precisa.

O que mudou e por quê

Antes de entrar nos passos de migração, aqui vai o panorama geral do que o Next.js 16 mudou e o motivo por trás de cada decisão:

┌─────────────────────────────────────────────────────────────┐
│                    Arquitetura Next.js 16                     │
│                                                              │
│  ┌──────────────┐  ┌──────────────┐  ┌─────────────────┐   │
│  │  Turbopack    │  │  proxy.ts    │  │  Cache           │   │
│  │  (Único       │  │  (Substitui  │  │  Components      │   │
│  │   Bundler)    │  │   middleware) │  │  ('use cache')   │   │
│  └──────┬───────┘  └──────┬───────┘  └────────┬────────┘   │
│         │                  │                    │             │
│  ┌──────┴───────┐  ┌──────┴───────┐  ┌────────┴────────┐   │
│  │  HMR & Build │  │  Limites de  │  │  Cache           │   │
│  │  10x mais    │  │  rede claros │  │  declarativo com │   │
│  │  rápido      │  │              │  │  TTL granular    │   │
│  └──────────────┘  └──────────────┘  └──────────────────┘   │
│                                                              │
│  ┌──────────────┐  ┌──────────────┐                         │
│  │  APIs de      │  │  PPR         │                         │
│  │  Request      │  │  (Padrão)    │                         │
│  │  Assíncronas  │  │              │                         │
│  └──────────────┘  └──────────────┘                         │
└─────────────────────────────────────────────────────────────┘
MudançaPor quê
Turbopack substitui WebpackA arquitetura do Webpack não conseguia escalar pra HMR sub-segundo com mais de ~500 módulos. O motor de computação incremental do Turbopack lida com grafos de 10K+ módulos com updates consistentes de <200ms.
proxy.ts substitui middleware.tsO middleware misturava lógica de request (auth, redirects) com comportamento de proxy de rede (rewriting, injeção de headers). proxy.ts clarifica qual código roda no edge vs. servidor.
APIs de request assíncronasparams, searchParams, cookies() e headers() agora são sempre async. Isso permite melhor streaming e elimina bloqueios implícitos no rendering de RSC.
Cache Components (use cache)Os padrões anteriores de revalidate, unstable_cache e cache() aninhados criavam uma hierarquia de cache imprevisível. use cache provê uma primitiva única, declarativa e componível.
PPR como padrãoPartial Prerendering agora é a estratégia de rendering padrão, combinando shells estáticos com conteúdo dinâmico streameado. Sem mais decidir entre SSR e SSG.

Passo 0: Antes de começar

1. Registre suas métricas atuais

Antes de mexer em código, capture sua performance atual:

# Baseline de performance npx next build 2>&1 | tee build-before.log npx @next/bundle-analyzer # Métricas de runtime lighthouse https://your-app.com --output json --output-path baseline.json

2. Verifique a compatibilidade do Node.js

Next.js 16 requer Node.js 20.x ou superior. Node 18 não tem mais suporte.

node -v # Deve ser >= v20.0.0

3. Execute o comando de upgrade

npx @next/codemod@latest upgrade

Isso cuida da maior parte da migração automatizada. Mas não pega tudo — o resto desse guia cobre o que o codemod não toca.

Passo 1: Turbopack — Webpack não é mais o padrão

A mudança mais visível: Turbopack agora é o bundler padrão pra dev e produção. A chave webpack no next.config.ts foi deprecada — mas se você tem loaders incompatíveis, dá pra fazer fallback temporário com next dev --webpack ou next build --webpack. Esse escape hatch existe pra migração, mas o objetivo é eliminá-lo.

O que quebra

Se você tem algo disso no seu next.config.ts, vai falhar:

// ❌ Isso não funciona mais no Next.js 16 module.exports = { webpack: (config) => { config.resolve.alias['@'] = path.resolve(__dirname, 'src'); config.module.rules.push({ test: /\.svg$/, use: ['@svgr/webpack'], }); return config; }, };

Como migrar

Pra aliases: Use os paths nativos do tsconfig.json — Turbopack reconhece nativamente:

// tsconfig.json { "compilerOptions": { "paths": { "@/*": ["./src/*"] } } }

Pra imports de SVG: Use @svgr/turbopack em vez de @svgr/webpack:

npm install @svgr/turbopack
// next.config.ts import type { NextConfig } from 'next'; const nextConfig: NextConfig = { turbopack: { rules: { '*.svg': { loaders: ['@svgr/turbopack'], as: '*.js', }, }, }, }; export default nextConfig;

Pra outros loaders custom: Verifique se existe versão compatível com Turbopack. A maioria dos loaders populares (CSS modules, SASS, otimização de imagem) vem built-in no Turbopack. Pro resto, a config turbopack.rules é sua saída de emergência.

Verificação

# Desenvolvimento npx next dev # Se iniciar sem erros, Turbopack tá funcionando # Build de produção npx next build

Se você ver erros Module not found durante o build que não existiam antes, é quase certeza um problema de compatibilidade de loader. Confira a tabela de compatibilidade do Turbopack pros seus loaders específicos.

Passo 2: middleware.ts → proxy.ts

Essa é a mudança que gera mais confusão. O antigo middleware.ts foi dividido em duas camadas conceituais:

  1. proxy.ts — Operações a nível de rede (reescrita de URLs, injeção de headers, roteamento por geolocalização). Roda no runtime Node.js por padrão (diferente do antigo middleware que defaultava pra Edge).
  2. Lógica server-side em route handlers — Checagens de autenticação, validação de sessão e lógica de negócio agora ficam nos seus route handlers, layouts ou server actions.

Como era um middleware.ts típico

// ❌ ANTES: middleware.ts (Next.js 15) import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { // Checagem de auth const token = request.cookies.get('session-token'); if (!token && request.nextUrl.pathname.startsWith('/dashboard')) { return NextResponse.redirect(new URL('/login', request.url)); } // Detecção de locale const locale = request.headers.get('Accept-Language')?.split(',')[0] || 'en'; const response = NextResponse.next(); response.headers.set('x-locale', locale); // Roteamento de A/B test const bucket = Math.random() > 0.5 ? 'a' : 'b'; response.headers.set('x-ab-bucket', bucket); return response; } export const config = { matcher: ['/((?!api|_next/static|favicon.ico).*)'], };

O novo proxy.ts

// ✅ AGORA: proxy.ts (Next.js 16) import type { NextRequest } from 'next/server'; export function proxy(request: NextRequest) { const url = request.nextUrl.clone(); // Detecção de locale (concern de rede) const locale = request.headers.get('Accept-Language')?.split(',')[0] || 'en'; // Roteamento de A/B test (concern de rede) const bucket = Math.random() > 0.5 ? 'a' : 'b'; return { headers: { 'x-locale': locale, 'x-ab-bucket': bucket, }, }; } export const config = { matcher: ['/((?!api|_next/static|favicon.ico).*)'], };

Pra onde vai a autenticação?

As checagens de auth vão pros layouts ou route handlers onde elas pertencem:

// app/dashboard/layout.tsx import { redirect } from 'next/navigation'; import { getSession } from '@/lib/auth'; export default async function DashboardLayout({ children, }: { children: React.ReactNode; }) { const session = await getSession(); if (!session) { redirect('/login'); } return <>{children}</>; }

Na real, essa é uma arquitetura melhor. A lógica de auth agora vive do lado das rotas que protege, fazendo o modelo de segurança explícito e auditável, em vez de ficar escondido num arquivo global de middleware.

Codemod

npx @next/codemod@latest middleware-to-proxy

O codemod lida com padrões simples de headers/rewrites mas não vai mover lógica de auth automaticamente. Revise o resultado com cuidado.

Passo 3: APIs de request assíncronas

Todas as APIs de request dinâmica agora são estritamente async. Essa é a mudança com maior impacto em número de arquivos — espere modificar cada page, layout e route handler que acessa params, search params, cookies ou headers.

Antes (Next.js 15)

// ❌ Acesso síncrono não funciona mais export default function Page({ params, searchParams, }: { params: { slug: string }; searchParams: { q?: string }; }) { const title = params.slug; const query = searchParams.q; return <div>{title} - {query}</div>; }

Depois (Next.js 16)

// ✅ Todas as APIs de request são async export default async function Page({ params, searchParams, }: { params: Promise<{ slug: string }>; searchParams: Promise<{ q?: string }>; }) { const { slug } = await params; const { q } = await searchParams; return <div>{slug} - {q}</div>; }

cookies() e headers()

// ❌ Antes import { cookies, headers } from 'next/headers'; export default function Page() { const cookieStore = cookies(); const headerList = headers(); // ... } // ✅ Depois import { cookies, headers } from 'next/headers'; export default async function Page() { const cookieStore = await cookies(); const headerList = await headers(); // ... }

Codemod

npx @next/codemod@latest async-request-apis

Esse codemod tem taxa de sucesso alta (~90%). Adiciona async nas funções, envolve com await, e atualiza as assinaturas de tipo. Confira os resultados — às vezes ele perde edge cases em funções utilitárias custom que passam params por referência.

Passo 4: Cache Components e a diretiva use cache

Essa é a maior mudança conceitual. O modelo de cache anterior (uma mistura de revalidate, unstable_cache, opções de cache do fetch e cache()) foi substituído por uma única primitiva componível: a diretiva use cache.

O mundo antigo (confuso)

// ❌ Next.js 15: Múltiplos mecanismos de cache sobrepostos export const revalidate = 3600; // revalidação a nível de página async function getData() { const res = await fetch('https://api.example.com/data', { next: { revalidate: 60, tags: ['data'] }, }); return res.json(); } // Mais: unstable_cache, cache(), generateStaticParams...

O mundo novo (declarativo)

Primeiro, habilite Cache Components na sua config:

// next.config.ts const nextConfig: NextConfig = { cacheComponents: true, // Obrigatório pra usar 'use cache' };

Depois use a diretiva:

// ✅ Next.js 16: Uma única diretiva 'use cache' import { cacheLife, cacheTag } from 'next/cache'; // Cache a nível de função async function getData() { 'use cache'; cacheLife('hours'); cacheTag('data'); const res = await fetch('https://api.example.com/data'); return res.json(); } // Cache a nível de componente async function ExpensiveWidget() { 'use cache'; cacheLife('days'); cacheTag('widget'); const data = await getData(); return <div>{data.title}</div>; } // Cache a nível de página export default async function Page() { 'use cache'; cacheLife('minutes'); return ( <main> <ExpensiveWidget /> <DynamicContent /> </main> ); }

Presets de Cache Life

Next.js 16 vem com presets de tempo de vida de cache embutidos:

PresetStaleRevalidateExpire
'seconds'0s1s60s
'minutes'5min1min1hr
'hours'5min1hr24hr
'days'5min1day14d
'weeks'5min1week30d
'max'5min30d365d

Você também pode definir perfis custom:

// next.config.ts const nextConfig: NextConfig = { cacheLife: { product: { stale: 300, // 5 minutos revalidate: 3600, // 1 hora expire: 86400, // 1 dia }, }, };
// Aí usa assim: async function getProduct(id: string) { 'use cache'; cacheLife('product'); cacheTag(`product-${id}`); return db.products.findById(id); }

Revalidação

A revalidação por tags funciona do mesmo jeito, mas agora é mais poderosa porque você pode taggear em qualquer nível de granularidade:

// app/api/revalidate/route.ts import { revalidateTag } from 'next/cache'; export async function POST(request: Request) { const { tag } = await request.json(); revalidateTag(tag); return Response.json({ revalidated: true }); }

Estratégia de migração

  1. Remova todos os export const revalidate = ... das páginas
  2. Substitua fetch(..., { next: { revalidate } }) por use cache + cacheLife
  3. Substitua chamadas de unstable_cache() por funções com use cache
  4. Adicione cacheTag() onde precisar de revalidação direcionada

Passo 5: PPR (Partial Prerendering) como padrão

PPR agora é a estratégia de rendering padrão. Isso significa que toda página automaticamente ganha um shell estático servido instantaneamente, com conteúdo dinâmico streameado via Suspense boundaries.

O que isso significa na prática

// Essa página automaticamente usa PPR no Next.js 16 export default async function ProductPage({ params, }: { params: Promise<{ id: string }>; }) { const { id } = await params; return ( <main> {/* Shell estático — servido da CDN */} <Header /> <ProductInfo id={id} /> {/* Conteúdo dinâmico — streameado */} <Suspense fallback={<PriceSkeleton />}> <DynamicPrice id={id} /> </Suspense> <Suspense fallback={<ReviewsSkeleton />}> <UserReviews id={id} /> </Suspense> </main> ); }

O ponto chave: componentes com use cache viram o shell estático. Componentes que acessam cookies, headers ou dados não cacheados viram os buracos dinâmicos que são streameados.

Se precisar desativar PPR

// next.config.ts const nextConfig: NextConfig = { experimental: { ppr: false, // Desativar PPR globalmente }, };

Ou por rota:

// app/legacy-page/page.tsx export const dynamic = 'force-dynamic'; // Essa página não usa PPR

Passo 6: Otimização de memória pra containers

Esse é o assassino silencioso. O pipeline de rendering RSC mais agressivo do Next.js 16 pode consumir significativamente mais memória que o v15, especialmente em ambientes containerizados com limites rígidos de memória.

O problema

Em deploys de Kubernetes ou Docker com limites de memória (ex: 512MB por pod), você pode ver OOM kills que não aconteciam no Next.js 15. A causa raiz é o grafo de módulos em memória do Turbopack e o motor de rendering RSC mantendo mais estado intermediário.

A solução

// next.config.ts const nextConfig: NextConfig = { turbopack: { memoryLimit: 256 * 1024 * 1024, // 256MB }, experimental: { incrementalCacheHandlerPath: './cache-handler.mjs', }, };
// cache-handler.mjs import { CacheHandler } from '@next/cache-handler-redis'; export default class CustomCacheHandler extends CacheHandler { constructor(options) { super({ ...options, redis: { url: process.env.REDIS_URL, }, inMemoryCacheEnabled: false, }); } }

Recomendações de recursos pra containers

Tamanho da appMemória recomendadaCPU recomendada
Pequena (<50 rotas)512MB0.5 vCPU
Média (50-200 rotas)1GB1 vCPU
Grande (200+ rotas)2GB2 vCPU

Monitore com:

# Vigiar uso de memória durante build docker stats --format "table {{.Name}}\t{{.MemUsage}}\t{{.MemPerc}}" # Perfilar memória do Node.js durante runtime NODE_OPTIONS="--max-old-space-size=1024 --heapsnapshot-near-heap-limit=3" npm start

Passo 7: Mudanças no next.config.ts

Várias opções de configuração foram renomeadas ou reestruturadas:

// next.config.ts — Configuração completa do Next.js 16 import type { NextConfig } from 'next'; const nextConfig: NextConfig = { turbopack: { rules: { '*.svg': { loaders: ['@svgr/turbopack'], as: '*.js', }, }, resolveAlias: { 'legacy-lib': './src/lib/legacy-adapter', }, }, cacheLife: { product: { stale: 300, revalidate: 3600, expire: 86400 }, blog: { stale: 60, revalidate: 900, expire: 86400 }, }, images: { remotePatterns: [ { protocol: 'https', hostname: '**.example.com' }, ], }, async redirects() { return [ { source: '/old-path', destination: '/new-path', permanent: true }, ]; }, }; export default nextConfig;

Opções removidas

Essas opções do next.config.ts não existem mais:

// ❌ Tudo isso foi removido no Next.js 16 { webpack: () => {}, // Usar turbopack.rules swcMinify: true, // Sempre ativo (Turbopack) experimental: { appDir: true, // Sempre ativo desde v14 serverActions: true, // Sempre ativo desde v15 typedRoutes: true, // Sempre ativo }, }

Checklist completo de migração

Passe por esse checklist depois de rodar os codemods:

Infraestrutura

  • Node.js >= 20.x instalado
  • next atualizado pra ^16.0.0
  • react e react-dom atualizados pra ^19.2.0
  • Todos os pacotes @next/* em versões compatíveis
  • Limites de memória dos containers revisados (aumentar se < 512MB)

Bundler

  • Removida toda config webpack do next.config.ts
  • Loaders custom migrados pra turbopack.rules
  • Paths do tsconfig.json verificados com Turbopack
  • npx next build executado sem erros

Routing

  • Migrado middleware.tsproxy.ts (só concerns de rede)
  • Lógica de auth movida de middleware pra layouts/route handlers
  • Patterns do matcher do proxy.ts revisados

Data Fetching

  • Todos os params e searchParams agora são Promise<T> com await
  • Todas as chamadas de cookies() e headers() têm await
  • Convertido export const revalidateuse cache + cacheLife
  • Habilitado cacheComponents: true no next.config.ts
  • Substituído unstable_cache → funções com use cache
  • Adicionado cacheTag() pra revalidação direcionada

Rendering

  • Verificado o comportamento do PPR com Suspense boundaries
  • Adicionados componentes skeleton pro conteúdo dinâmico
  • Testado dynamic = 'force-dynamic' onde PPR não é desejado

Produção

  • Benchmarkado o tempo de build (esperar melhora com Turbopack)
  • Load-testado com padrões de tráfego de produção
  • Monitorado uso de memória em containers por 24 horas
  • Verificadas as taxas de cache hit em produção

Timeline real de migração

Baseado em migrações de produção em times de diferentes tamanhos:

Tamanho da appCobertura do codemodTrabalho manualTempo total
Pequena (<50 rotas)~85%1-2 dias3-4 dias
Média (50-200 rotas)~75%3-5 dias1-2 semanas
Grande (200+ rotas)~60%1-2 semanas3-4 semanas

O que consome mais tempo:

  1. Loaders custom de Webpack → encontrar equivalentes no Turbopack
  2. Lógica complexa de middleware → decompor em proxy + auth no layout
  3. Redesign da estratégia de cache → mapear padrões antigos de revalidate pra use cache

O que esperar depois da migração

Depois de uma migração limpa, times tipicamente reportam:

  • Início do dev server: 60-80% mais rápido (Turbopack vs Webpack)
  • Updates de HMR: 5-10x mais rápidos (consistentemente <200ms)
  • Build de produção: 20-40% mais rápido
  • TTFB: 30-50% de melhora com PPR (shell estático servido instantaneamente)
  • Uso de memória: Similar ou ligeiramente maior (requer tuning)

Os ganhos de performance do Turbopack e PPR sozinhos justificam o esforço de migração. A clareza arquitetural do proxy.ts e use cache é um investimento de longo prazo que dá dividendos conforme sua app cresce.

Next.js 16 é opinativo. Te força pra padrões que são objetivamente melhores mas requerem trabalho de migração antecipado. Os codemods cuidam das mudanças mecânicas. As mudanças arquiteturais — separar proxy de auth, adotar cache declarativo, abraçar PPR — essas requerem entendimento. Esse guia te deu os dois. Bora atualizar.

Next.jsNext.js 16migraçãoTurbopackReactTypeScriptdesenvolvimento webfrontendproxycache componentsPPR

Explore ferramentas relacionadas

Experimente estas ferramentas gratuitas do Pockit