O Next.js 15 foi uma versão de mudanças de comportamento, mais do que de recursos chamativos. Ele tornou o cache explícito, transformou as APIs de requisição em assíncronas e trouxe o React 19 para o App Router. Este site, inclusive, roda em Next.js 15.

Neste guia você vai entender cada mudança, o motivo por trás dela e como migrar um projeto existente sem sustos.

Criando e atualizando um projeto

Bash
npx create-next-app@15 meu-app        # projeto novo na linha 15
npx @next/codemod@canary upgrade latest   # atualiza um projeto existente e roda os codemods

O comando de upgrade atualiza next, react e react-dom e aplica automaticamente boa parte das mudanças de código descritas abaixo.

APIs de requisição agora são assíncronas

Esta é a mudança que mais quebra código. Tudo que depende da requisição passou a devolver uma Promise: cookies(), headers(), draftMode() e as props params e searchParams de páginas, layouts e rotas.

Antes (Next.js 14)
TSX
export default function Pagina({ params }: { params: { slug: string } }) {
  return <h1>{params.slug}</h1>;
}
Depois (Next.js 15)
TSX
export default async function Pagina({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  return <h1>{slug}</h1>;
}
TSX
import { cookies, headers } from "next/headers";
 
export default async function Painel() {
  const tema = (await cookies()).get("tema")?.value ?? "escuro";
  const agente = (await headers()).get("user-agent");
  return <p>{tema} · {agente}</p>;
}

Por quê? Com as APIs assíncronas, o servidor pode começar a renderizar tudo que não depende da requisição antes de os dados dela estarem prontos. Isso prepara o terreno para renderização parcial (parte estática, parte dinâmica na mesma página).

Em Client Components, que não podem ser async, use o use do React para ler a Promise:

TSX
"use client";
import { use } from "react";
 
export default function Cliente({ params }: { params: Promise<{ id: string }> }) {
  const { id } = use(params);
  return <p>{id}</p>;
}

O codemod next-async-request-api converte a maior parte dos casos automaticamente.

O cache deixou de ser implícito

No Next.js 14, muita coisa era cacheada por padrão, e isso confundia: dados desatualizados apareciam sem motivo aparente. O 15 inverteu a lógica.

O quêNext.js 14Next.js 15
fetch() em Server ComponentsCacheado por padrãoNão cacheado por padrão
Route Handlers GETEstáticos por padrãoDinâmicos por padrão
Router cache no cliente (páginas)Reutilizado por 30 sSempre busca a versão nova ao navegar

Para cachear, agora você pede explicitamente:

TSX
// Um fetch específico
const res = await fetch("https://api.exemplo.com/produtos", {
  cache: "force-cache",
});
 
// Revalidar a cada hora
const posts = await fetch("https://api.exemplo.com/posts", {
  next: { revalidate: 3600 },
});
TSX
// Todo o segmento
export const dynamic = "force-static";
export const revalidate = 3600;
app/api/status/route.ts
TypeScript
export const dynamic = "force-static";   // volta a tornar o GET estático
 
export async function GET() {
  return Response.json({ ok: true });
}

React 19

O App Router no Next.js 15 usa o React 19, que traz:

  • Actions: funções assíncronas passadas para <form action>, com estado de envio gerenciado pelo React.
  • useActionState: estado e resultado de uma action (substitui o antigo useFormState).
  • useFormStatus: saber se o formulário pai está enviando.
  • useOptimistic: atualizar a interface antes da resposta do servidor.
  • use: ler Promises e contextos, inclusive condicionalmente.
  • ref como prop comum: forwardRef deixa de ser necessário.
  • Metadados no componente: <title> e <meta> renderizados em qualquer lugar sobem para o <head>.
TSX
"use client";
import { useActionState } from "react";
import { assinar } from "./actions";
 
export function Newsletter() {
  const [estado, acao, enviando] = useActionState(assinar, { mensagem: "" });
 
  return (
    <form action={acao}>
      <input name="email" type="email" required />
      <button disabled={enviando}>{enviando ? "Enviando..." : "Assinar"}</button>
      <p>{estado.mensagem}</p>
    </form>
  );
}
actions.ts
TypeScript
"use server";
 
export async function assinar(_anterior: { mensagem: string }, form: FormData) {
  const email = form.get("email");
  // ... grava no banco
  return { mensagem: `Inscrito: ${email}` };
}

O React Compiler, que aplica memoização automaticamente e reduz a necessidade de useMemo e useCallback, pode ser ativado de forma experimental com experimental.reactCompiler no next.config.

Turbopack no desenvolvimento

O Turbopack, o empacotador escrito em Rust, ficou estável para o servidor de desenvolvimento:

Bash
next dev --turbopack

Os ganhos anunciados pela Vercel foram significativos no início do servidor e na atualização após salvar arquivos, especialmente em projetos grandes. O suporte ao next build com Turbopack chegou depois, em fase experimental, dentro da linha 15.

next.config.ts

A configuração agora pode ser escrita em TypeScript, com autocomplete:

next.config.ts
TypeScript
import type { NextConfig } from "next";
 
const nextConfig: NextConfig = {
  reactStrictMode: true,
  images: { formats: ["image/avif", "image/webp"] },
};
 
export default nextConfig;

after(): trabalho depois da resposta

Executa código depois que a resposta já foi enviada ao usuário, ideal para logs, analytics e sincronizações que não devem atrasar a página. Chegou como experimental no 15.0 e ficou estável no 15.1:

TSX
import { after } from "next/server";
 
export default async function Pagina() {
  after(async () => {
    await registrarVisualizacao();   // não atrasa a renderização
  });
 
  return <h1>Olá</h1>;
}

next/form

O componente <Form> estende o <form> HTML com navegação do lado do cliente, prefetch da página de destino e envio progressivo. Perfeito para formulários de busca que levam para uma página de resultados:

TSX
import Form from "next/form";
 
export function Busca() {
  return (
    <Form action="/busca">
      <input name="q" placeholder="Buscar..." />
      <button type="submit">Buscar</button>
    </Form>
  );
}

Ao enviar, o usuário vai para /busca?q=... sem recarregar a página inteira.

Outras melhorias

  • Indicador de rota estática durante o desenvolvimento, mostrando quais páginas serão geradas no build.
  • instrumentation.ts estável, com onRequestError para enviar erros do servidor a ferramentas de observabilidade.
  • Segurança em Server Actions: IDs imprevisíveis e remoção de actions não utilizadas do build.
  • Melhorias para self-hosting: controle de Cache-Control e otimização de imagens usando sharp automaticamente.
  • ESLint 9 suportado.

Passo a passo de migração

  1. Crie uma branch e rode o upgrade

    Bash
    git switch -c chore/next-15
    npx @next/codemod@canary upgrade latest
  2. Revise as APIs assíncronas

    Procure usos de params, searchParams, cookies() e headers() que o codemod não converteu (ele deixa comentários @next-codemod-error nos casos que não conseguiu resolver).

  3. Decida o cache de propósito

    Liste os fetch e Route Handlers GET que dependiam do cache implícito e adicione cache: "force-cache", revalidate ou dynamic = "force-static" onde fizer sentido.

  4. Atualize bibliotecas para o React 19

    Bibliotecas de UI e de formulários podem precisar de versões novas. Rode npm ls react para achar dependências presas ao React 18.

  5. Compile e teste

    Bash
    npm run build
    npm run start

    Confira no resumo do build quais rotas ficaram estáticas (○) e quais dinâmicas (ƒ) e compare com o que você esperava.

Tabela de referência do Next.js 15

Cheat sheet

Next.js 15: referência rápida

Comandos e APIs mais usados.

CLI

No terminal

ComandoDescrição
npx create-next-app@15 appCria projeto
next dev --turbopackDesenvolvimento com Turbopack
next buildBuild de produção
next startServidor de produção
next lintESLint
npx @next/codemod@canary upgrade latestAtualiza e migra
next infoInformações para abrir issues

Dados e cache

Explícito no Next.js 15

ComandoDescrição
fetch(url, { cache: "force-cache" })Cacheia a requisição
fetch(url, { next: { revalidate: 60 } })Revalida a cada 60 s
export const revalidate = 3600Revalidação do segmento
export const dynamic = "force-static"Força estático
revalidatePath("/blog")Invalida uma rota
revalidateTag("posts")Invalida por tag
generateStaticParams()Rotas dinâmicas no build

Requisição

Agora assíncronas

ComandoDescrição
const { slug } = await paramsParâmetros da rota
const sp = await searchParamsQuery string
(await cookies()).get("x")Lê cookie
(await headers()).get("x")Lê header
use(params)Em Client Components
after(() => ...)Executa após responder
redirect("/login") / notFound()Redireciona / 404

React 19

Hooks e padrões novos

ComandoDescrição
"use server"Marca Server Actions
"use client"Marca Client Components
useActionState(acao, inicial)Estado de uma action
useFormStatus()Formulário enviando?
useOptimistic(estado, fn)Atualização otimista
use(promise)Lê uma Promise
<Form action="/busca">Formulário com navegação client-side

Referências

Teste seus conhecimentos
1 / 5

5 perguntas sobre o Next.js 15.

Como ler params em uma página do Next.js 15?

Não foi dessa vez. Explicação:

params passou a ser uma Promise e precisa ser aguardado.