Next.js 16 e React 19 no e-commerce: SSR, dashboards e produtos com login
O cavalo de batalha para storefronts complexos, painéis de pedidos e áreas logadas no varejo mid-market
Alexandre Caramaschi
CEO da Brasil GEO, ex-CMO da Semantix (Nasdaq), cofundador da AI Brasil
Há um momento, no varejo mid-market, em que o site deixa de ser vitrine e vira aplicação. A loja de joalheria que antes só listava anéis agora tem conta do cliente, carrinho que sobrevive entre sessões, painel de pedidos e um dashboard de estoque que o operador consulta o dia inteiro. Quando esse momento chega, a pergunta sobre framework muda de figura. Não se trata mais de qual gera o HTML mais leve, e sim de qual sustenta uma aplicação completa com login, dados dinâmicos e renderização no servidor sem virar um quebra-cabeça de estado.
Next.js é o cavalo de batalha desse cenário. A versão 16, lançada em 21 de outubro de 2025 sobre o React 19, consolidou decisões que vinham sendo experimentais e endereçou de frente o dilema que mais atormenta o e-commerce: como ter uma casca estática rápida e indexável convivendo com dados que mudam a cada requisição. Este guia percorre o que mudou e mostra seis exemplos mínimos e corretos, todos amarrados ao varejo brasileiro de moda, joalheria e cosméticos.
O que o Next.js 16 traz de concreto para um e-commerce?
Resposta direta: Turbopack como bundler padrão e estável, App Router com React Server Components por padrão, Cache Components com a diretiva "use cache", e uma quebra importante de compatibilidade — params e searchParams agora são assíncronos.
O Turbopack deixou de ser flag opcional e virou o bundler padrão para todos os apps. Builds ficam de duas a cinco vezes mais rápidos e o Fast Refresh chega a ser dez vezes mais ágil no desenvolvimento. Para um time pequeno de varejo que itera muito a vitrine, isso é tempo de ciclo recuperado. Se algum plugin legado ainda não suporta, o opt-out existe.
# Dev e build usam Turbopack por padrão no Next.js 16
next dev
next build
# Opt-out explícito para casos legados
next dev --webpack
next build --webpack
Vale internalizar a regra de plataforma antes de seguir: Node.js 20.9 ou superior e TypeScript 5.1 ou superior são requisitos. Abaixo disso o build nem inicia.
A mudança que mais quebra código de projetos antigos é a assincronia. params, searchParams, cookies(), headers() e draftMode() passaram a retornar promessas. Esquecer o await é o erro número um de quem migra.
Como ler um filtro de catálogo no servidor com searchParams assíncrono?
Resposta direta: a página é um Server Component assíncrono, você dá await no searchParams para ler o filtro da URL e busca os produtos no servidor, devolvendo HTML pronto.
Esse é o caso mais comum de uma loja: a página de categoria que aceita ?categoria=aneis e renderiza a grade já filtrada. Como o Server Component roda no servidor, o crawler do motor de IA e o do Google recebem a lista completa na primeira resposta, sem depender de JavaScript no cliente. É o coração da estratégia de GEO para catálogo.
// app/catalogo/page.tsx — Server Component (sem "use client")
import { buscarProdutos } from "@/lib/produtos";
import { GradeProdutos } from "@/components/GradeProdutos";
export default async function CatalogoPage({
searchParams,
}: {
searchParams: Promise<{ categoria?: string }>;
}) {
const { categoria = "todos" } = await searchParams;
const produtos = await buscarProdutos({ categoria });
return (
<main>
<h1>Catálogo — {categoria === "todos" ? "Tudo" : categoria}</h1>
<GradeProdutos itens={produtos} />
</main>
);
}
Repare em dois detalhes. O tipo de searchParams é uma Promise, e você só extrai os valores depois do await. E buscarProdutos roda no servidor, então pode falar direto com o banco ou com a API headless de catálogo sem expor chave nenhuma ao navegador. Quem trata o backend como fonte de verdade legível colhe o benefício também na citação por IA, tema do guia Backend legível decide a citação em IA.
Como fazer um login com Server Action e estado de pending?
Resposta direta: você escreve uma função com "use server" que valida credenciais e cria a sessão, e conecta ao formulário com useActionState, que devolve estado, a action do formulário e a flag isPending.
Produto com login é a fronteira entre vitrine e aplicação. O React 19 trouxe Actions — funções assíncronas que gerenciam pending, erro e estado otimista — e o gancho useActionState para amarrar tudo a um <form> sem reinventar controle de submit.
// app/login/actions.ts
"use server";
import { criarSessao, validarCredenciais } from "@/lib/auth";
export type EstadoLogin = { erro?: string };
export async function entrar(
_prev: EstadoLogin,
formData: FormData,
): Promise<EstadoLogin> {
const email = String(formData.get("email") ?? "");
const senha = String(formData.get("senha") ?? "");
const cliente = await validarCredenciais(email, senha);
if (!cliente) {
return { erro: "E-mail ou senha incorretos." };
}
await criarSessao(cliente.id);
return {};
}
// app/login/page.tsx — Client Component
"use client";
import { useActionState } from "react";
import { entrar, type EstadoLogin } from "./actions";
const inicial: EstadoLogin = {};
export default function LoginPage() {
const [estado, formAction, isPending] = useActionState(entrar, inicial);
return (
<form action={formAction}>
<input name="email" type="email" required />
<input name="senha" type="password" required />
{estado.erro && <p role="alert">{estado.erro}</p>}
<button type="submit" disabled={isPending}>
{isPending ? "Entrando..." : "Entrar"}
</button>
</form>
);
}
A action roda no servidor — validarCredenciais e criarSessao nunca chegam ao navegador. O useActionState recebe a action e um estado inicial, e devolve a tripla [estado, formAction, isPending]. O botão desabilita sozinho durante o envio, e a mensagem de erro vem do retorno da própria action. Sem useState para pending, sem fetch manual.
A diretiva
"use server"transforma a função num endpoint. Trate cada Server Action como rota de API: valide entrada, valide sessão e jamais confie em dado vindo do cliente. A conveniência não dispensa a checagem.
Como servir um dashboard de pedidos com casca estática e dados frescos?
Resposta direta: você ativa Cache Components, marca a casca da página com "use cache" para servir HTML instantâneo, e isola a parte dinâmica sob <Suspense>, revalidando por tag quando um pedido muda.
Esse é o exemplo que melhor mostra o salto do Next.js 16. O Partial Prerendering — completado pelos Cache Components — entrega o cabeçalho, a navegação e a moldura do dashboard como casca estática rapidíssima, enquanto a tabela de pedidos, que muda toda hora, renderiza dinâmica via streaming dentro de um <Suspense>.
// app/painel/page.tsx
import { Suspense } from "react";
import { TabelaPedidos } from "@/components/TabelaPedidos";
// Casca cacheada: cabeçalho e moldura servem como HTML estático e veloz
async function CascaPainel({ children }: { children: React.ReactNode }) {
"use cache";
return (
<section>
<h1>Painel de pedidos</h1>
<p>Acompanhamento de vendas — loja de cosméticos</p>
{children}
</section>
);
}
export default function PainelPage() {
return (
<CascaPainel>
<Suspense fallback={<p>Carregando pedidos...</p>}>
<TabelaPedidos />
</Suspense>
</CascaPainel>
);
}
// app/painel/actions.ts
"use server";
import { revalidateTag } from "next/cache";
import { atualizarStatusPedido } from "@/lib/pedidos";
export async function marcarEnviado(pedidoId: string) {
await atualizarStatusPedido(pedidoId, "enviado");
// 2º argumento obrigatório: perfil de cacheLife para stale-while-revalidate
revalidateTag("pedidos", "max");
}
A casca com "use cache" é cacheada pelo compilador, que gera a chave automaticamente. A TabelaPedidos permanece dinâmica e entra por streaming. Quando o operador marca um pedido como enviado, a Server Action chama revalidateTag("pedidos", "max") — e aqui está uma novidade do 16: revalidateTag agora exige um perfil de cacheLife como segundo argumento, habilitando o comportamento de stale-while-revalidate. O cliente vê o dado antigo por um instante enquanto o novo é revalidado em segundo plano.
Para mutações com leitura imediata do próprio valor escrito, o 16 introduziu updateTag(tag), usável só em Server Actions, com semântica read-your-writes — expira e relê o dado na mesma requisição. E refresh(), também só em Server Action, atualiza dados que não estão cacheados.
Como proteger uma rota autenticada com proxy.ts?
Resposta direta: proxy.ts substitui middleware.ts e roda no runtime Node; você exporta uma função proxy que verifica a sessão e redireciona para o login quando ela falta.
O Next.js 16 aposentou o middleware.ts em favor de proxy.ts. O middleware.ts segue funcionando no Edge, mas está deprecado. O proxy.ts roda no Node, o que dá acesso a APIs de servidor completas — bom para validar sessão antes de a página renderizar.
// proxy.ts (na raiz do projeto)
import { NextRequest, NextResponse } from "next/server";
export default function proxy(request: NextRequest) {
const sessao = request.cookies.get("sessao")?.value;
const protegida = request.nextUrl.pathname.startsWith("/painel");
if (protegida && !sessao) {
const url = request.nextUrl.clone();
url.pathname = "/login";
return NextResponse.redirect(url);
}
return NextResponse.next();
}
export const config = {
matcher: ["/painel/:path*", "/conta/:path*"],
};
O matcher limita o proxy às rotas que importam — painel e área de conta. Sem cookie de sessão numa rota protegida, o cliente é redirecionado ao login antes de qualquer renderização. É a primeira linha de defesa da área logada, complementada pela validação dentro de cada Server Action.
Como dar feedback instantâneo ao adicionar ao carrinho?
Resposta direta: useOptimistic mostra o item no carrinho imediatamente, antes da confirmação do servidor, e reconcilia quando a Server Action responde.
No varejo, a latência percebida derruba conversão. Quando o cliente toca em “adicionar”, ele precisa ver o carrinho reagir na hora. O useOptimistic do React 19 mantém um estado otimista que se atualiza na frente do servidor e volta ao real quando a resposta chega.
// components/BotaoCarrinho.tsx
"use client";
import { useOptimistic, startTransition } from "react";
import { adicionarItem } from "@/app/carrinho/actions";
type Item = { sku: string; nome: string };
export function BotaoCarrinho({
carrinho,
produto,
}: {
carrinho: Item[];
produto: Item;
}) {
const [otimista, addOtimista] = useOptimistic(
carrinho,
(estado, novo: Item) => [...estado, novo],
);
function aoAdicionar() {
startTransition(() => {
addOtimista(produto); // UI reage na hora
adicionarItem(produto.sku); // Server Action confirma no servidor
});
}
return (
<div>
<button onClick={aoAdicionar}>Adicionar ao carrinho</button>
<span>{otimista.length} itens</span>
</div>
);
}
O contador sobe no mesmo instante do clique. Se a Server Action adicionarItem falhar, o React reverte o estado otimista sozinho ao valor confirmado pelo servidor. O segredo está no detalhe que o guia já alertou: estado otimista é experiência, não verdade. A verdade do carrinho mora no servidor, validada pela action — tema aprofundado em Carrinho, checkout, carteiras, Pix e fluxos agênticos.
Como configurar Cache Components e React Compiler no next.config?
Resposta direta: você ativa cacheComponents: true para liberar a diretiva "use cache" e, opcionalmente, reactCompiler: true para memoização automática.
Os Cache Components são opt-in. Sem essa flag, a diretiva "use cache" não funciona — e, por padrão, tudo é dinâmico em tempo de requisição, coerente com a postura de app full-stack do framework.
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
// Libera a diretiva "use cache" e o Partial Prerendering completo
cacheComponents: true,
// React Compiler estável: memoização automática.
// Não é padrão; ligue e meça antes de adotar amplamente.
reactCompiler: true,
};
export default nextConfig;
O React Compiler estabilizou e memoiza componentes automaticamente, reduzindo a necessidade de useMemo e useCallback escritos à mão. Ele não vem ligado por padrão; o conselho é ativar, medir e comparar. Em dashboards de varejo com tabelas grandes e muitas re-renderizações, o ganho aparece. Em páginas simples, talvez nem se note.
Vale registrar que o React 19.2, que acompanha o App Router, ainda trouxe View Transitions, useEffectEvent() e o componente <Activity/>, além de metadados de documento nativos — você renderiza <title> e <meta> dentro de componentes, útil para SEO e GEO de fichas de produto sem bibliotecas extras.
Próximo passo
Para o e-commerce mid-market que virou aplicação — login, carrinho persistente, dashboards, dados por requisição — Next.js 16 com Turbopack, Cache Components e React 19 Actions é a aposta de produção. O caminho prático: ligar cacheComponents, mover toda leitura de params e searchParams para await, trocar middleware.ts por proxy.ts, e tratar cada Server Action como endpoint validado.
A escolha de framework, porém, depende do peso da aplicação. Para vitrine e conteúdo editorial premium com pouco JavaScript, compare com Astro islands para storefront e conteúdo. Para storefront reativo e leve com runtime mínimo, veja Svelte 5 e SvelteKit para storefront reativo. E para o critério de decisão lado a lado entre os três, consulte Stack de frontend para e-commerce: Astro, Next e Svelte.
A arquitetura por trás dessa escolha amarra com a tese composable: Composable, MACH, headless e API-first e Composable commerce com arquitetura agent-ready. Next.js é a peça de aplicação dessa arquitetura — onde o cliente loga, decide e compra.
Perguntas frequentes
Quando escolher Next.js 16 em vez de Astro ou SvelteKit para um e-commerce?
Quando o site é uma aplicação, não uma vitrine. Áreas logadas, carrinho persistente, dashboards de pedido, dados que mudam por requisição e fluxos de checkout pedem o App Router com Server Components e Server Actions. Para conteúdo majoritariamente estático e editorial, Astro costuma render menos JavaScript.
O que muda de fato ao migrar para o Next.js 16?
O ponto mais sensível é a assincronia: params, searchParams, cookies, headers e draftMode passam a retornar promessas e exigem await. middleware.ts foi substituído por proxy.ts no runtime Node. Turbopack virou o bundler padrão, então builds e dev ficam mais rápidos sem configuração extra.
Cache Components serve para um catálogo que muda de preço toda hora?
Serve, e é exatamente o caso de uso. Você marca a casca da página com use cache para servir HTML instantâneo e indexável, e isola preço, estoque e carrinho sob Suspense para renderizar dinâmico em tempo de requisição. revalidateTag com perfil de cacheLife faz o stale-while-revalidate sem rebuild.
Server Actions são seguras para login e pagamento?
A diretiva use server expõe a função como endpoint, então toda Server Action precisa validar entrada e sessão como qualquer rota de API. Use para a mutação e a orquestração; mantenha segredos e chamadas a gateway de pagamento no servidor. Nunca confie no estado otimista do cliente como verdade.
React Compiler já vale a pena ligar?
Ele estabilizou e memoiza componentes automaticamente, dispensando boa parte de useMemo e useCallback. Não vem ligado por padrão; ative com reactCompiler: true no next.config e meça. Em dashboards com muitas re-renderizações o ganho é perceptível.
Para levar deste guia
-
Next.js 16 entrega Turbopack como bundler estável padrão (builds 2-5x mais rápidos, Fast Refresh até 10x) e App Router com React Server Components por padrão.
-
A mudança que mais quebra código antigo: params, searchParams, cookies, headers e draftMode agora são assíncronos — exigem await.
-
Cache Components com a diretiva use cache e o Partial Prerendering resolvem o dilema do varejo: casca estática indexável e veloz convivendo com dados de pedido e estoque sempre frescos.
-
React 19 Actions, useActionState, useOptimistic e Server Actions cobrem login, carrinho e mutações sem reinventar gerenciamento de estado de formulário.
-
Para GEO, SSR de catálogo e ficha de produto entrega HTML completo para o crawler do motor de IA na primeira resposta, sem depender de execução de JavaScript.