Quando sua loja precisa virar um aplicativo de verdade
Login, painel de pedidos e carrinho que não se perde pedem uma tecnologia diferente da vitrine simples. Veja o que perguntar ao seu time técnico.
Alexandre Caramaschi
Founder da Brasil GEO, ex-CMO da Semantix (Nasdaq), cofundador da AI Brasil
Existe um momento em que o site da loja deixa de ser só uma vitrine e vira um programa de verdade. É quando aparece login do cliente, carrinho que não se perde entre visitas, e um painel de pedidos que alguém consulta o dia inteiro.
Cenário: uma loja de joias que antes só mostrava anéis agora tem conta de cliente, carrinho salvo e um painel de estoque conferido o dia todo. Nesse momento, a pergunta sobre qual tecnologia usar muda de figura.
Este texto traduz, em termos simples, o que o Next.js (uma das tecnologias mais usadas para esse tipo de site) resolve. A ideia é você entender o suficiente para conversar com o seu time técnico sobre o assunto. O código a seguir é referência para quem vai programar, não leitura obrigatória para o dono da loja.
Sinais de que a loja virou aplicação
O que o Next.js 16 traz de concreto para um e-commerce?
Resposta direta: Turbopack vira o bundler padrão e estável, e o App Router usa React Server Components por padrão. Há 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.
O que o Next.js 16 traz de concreto
- 1Turbopack como bundler padrãoEstável para todos os apps, com opt-out por --webpack.
- 2App Router com React Server ComponentsRenderização no servidor por padrão.
- 3Cache ComponentsDiretiva "use cache" para a casca estática.
- 4params e searchParams assíncronosQuebra de compatibilidade: exigem await.
# 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.
Cinco leituras que agora devolvem promessa
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, busca os produtos no servidor e devolve 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. Isso não depende de JavaScript no cliente. É o coração da estratégia de GEO para catálogo.
O filtro de catálogo resolvido no servidor
// 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. Depois conecta ao formulário com useActionState, que devolve estado, a action 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. O gancho useActionState amarra tudo a um <form> sem reinventar controle de envio.
O ciclo do login com Server Action
- O formulário envia e-mail e senhaaction={formAction}, sem fetch manual.
- entrar() roda no servidorMarcada com "use server".
- validarCredenciais decideSem cliente, devolve { erro } para o formulário.
- criarSessao(cliente.id)A sessão nasce no servidor.
- useActionState devolve a tripla[estado, formAction, isPending] amarra tudo ao form.
// 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.
Submit à mão e com useActionState
Controle reinventado
- useState para pending
- fetch manual no envio
- Erro tratado à mão no cliente
Com Actions do React 19
- isPending vem do gancho e o botão desabilita sozinho
- A mensagem de erro vem do retorno da própria action
- validarCredenciais e criarSessao nunca chegam ao navegador
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 e marca a casca da página com "use cache" para servir HTML instantâneo. 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 cabeçalho, a navegação e a moldura do dashboard viram casca estática rapidíssima. Já a tabela de pedidos, que muda toda hora, renderiza dinâmica via streaming dentro de um <Suspense>.
Casca estática com ilha dinâmica
- 1CascaPainel com "use cache"Cabeçalho, navegação e moldura como HTML estático, com chave gerada pelo compilador.
- 2Suspense com fallbackMostra "Carregando pedidos..." enquanto a tabela chega.
- 3TabelaPedidos dinâmicaRenderiza via streaming, muda toda hora.
- 4Server Action marcarEnviadoChama revalidateTag("pedidos", "max") quando o pedido muda.
// 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"). 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.
Antes dos Cache Components, o varejo vivia o falso dilema entre página estática rápida e dados frescos. Ou você prerenderizava tudo e mostrava preço velho, ou tornava tudo dinâmico e perdia velocidade e indexação. A casca estática com ilhas dinâmicas sob <Suspense> dissolve essa escolha. O crawler de IA recebe o conteúdo estável de imediato, e o cliente logado vê seu pedido em tempo real.
Página rápida ou dado fresco
Antes dos Cache Components
- Prerenderizar tudo e mostrar preço velho
- Ou tornar tudo dinâmico e perder velocidade e indexação
Casca estática com ilhas sob Suspense
- O crawler de IA recebe o conteúdo estável de imediato
- O cliente logado vê seu pedido em tempo real
- Dado antigo por um instante enquanto o novo revalida em segundo plano
Para mutações com leitura imediata do próprio valor escrito, o 16 introduziu updateTag(tag). Ele é usável só em Server Actions: expira e relê o dado na mesma requisição. Já refresh(), também só em Server Action, atualiza dados que não estão cacheados.
Três chamadas de cache do Next.js 16
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.
middleware.ts e proxy.ts
middleware.ts
- Roda no Edge
- Segue funcionando, mas está deprecado
proxy.ts
- Roda no runtime Node
- Acesso a APIs de servidor completas
- Valida a 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.
O que o proxy faz com cada requisição
- SeRota em /painel ou /conta sem cookie de sessãoentãoRedireciona para /login antes de qualquer renderização
- SeRota protegida com cookie de sessãoentãoNextResponse.next() deixa a página renderizar, e cada Server Action valida de novo
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.
O clique otimista, passo a passo
- Clique chama aoAdicionarDentro de startTransition.
- addOtimista(produto) reage na horaO contador sobe no mesmo instante.
- adicionarItem(sku) confirma no servidorA Server Action valida e grava.
- Se falhar, o React reverte sozinhoO estado volta ao valor confirmado pelo servidor.
// 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 e não verdade. A verdade do carrinho mora no servidor, validada pela action, tema aprofundado em Carrinho, checkout, carteiras, Pix e fluxos agênticos.
Estado otimista é experiência
A verdade do carrinho mora no servidor, validada pela action. O useOptimistic mostra o item antes da confirmação e reconcilia quando a resposta chega; ele nunca substitui a validação no servidor.
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.
Duas flags do next.config.ts
- SeQuer a diretiva "use cache" e o Partial Prerendering completoentãocacheComponents: true; sem a flag, tudo é dinâmico em tempo de requisição
- SeDashboard com tabelas grandes e muitas re-renderizaçõesentãoreactCompiler: true, depois medir e comparar
- SePágina simplesentãoO ganho do compilador talvez nem se note
// 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 depois comparar. Em dashboards de varejo com tabelas grandes e muitas re-renderizações, o ganho aparece. Em páginas simples, talvez nem se note.
O React 19.2 ainda trouxe View Transitions, useEffectEvent() e o componente <Activity/>. Ele também traz metadados de documento nativos: você renderiza <title> e <meta> dentro de componentes, útil para SEO e GEO de fichas de produto sem bibliotecas extras.
O que o React 19.2 acrescenta
O que decidir com o seu time técnico?
Se a sua loja já tem login, carrinho salvo e painel de pedidos, essa tecnologia é uma aposta segura. Passe esta lista ao seu desenvolvedor: ativar os recursos de cache, atualizar a leitura de dados da URL. Trocar o sistema antigo de proteção de rota, e tratar cada ação de servidor como uma porta que precisa de validação.
Caminho prático em quatro movimentos
- Ligar cacheComponentsLibera "use cache" e o Partial Prerendering.
- Mover params e searchParams para awaitToda leitura vira assíncrona.
- Trocar middleware.ts por proxy.tsSessão validada no runtime Node.
- Tratar cada Server Action como endpoint validadoA verdade mora no servidor.
A escolha certa depende do tamanho da sua loja. Para uma vitrine simples, com pouco código rodando no navegador do cliente, existem tecnologias mais leves, comparadas no guia como escolher a tecnologia certa para o seu site.
Qual framework para qual peso de aplicação
- SeVitrine e conteúdo editorial premium com pouco JavaScriptentãoAstro islands para storefront e conteúdo
- SeStorefront reativo e leve com runtime mínimoentãoSvelte 5 e SvelteKit
- SeLogin, carrinho persistente, dashboards e dados por requisiçãoentãoNext.js 16 com Turbopack, Cache Components e React 19 Actions
No começo deste texto, a loja tinha só uma vitrine. Agora você sabe que login, carrinho e painel de pedidos pedem uma tecnologia própria, e tem uma lista pronta para levar ao seu time técnico.
Perguntas frequentes
Quando escolher Next.js 16 em vez de Astro ou SvelteKit para um e-commerce?
Quando o site é uma aplicação e 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. 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
-
Quando a loja ganha login, carrinho e painel de pedidos, ela vira um programa de verdade, e isso pede uma tecnologia diferente da vitrine simples.
-
A tecnologia usada aqui é o Next.js, na versão 16. Ela entrega a página já pronta do servidor (SSR) e ainda atualiza o dado na hora.
-
Esse tipo de site mostra produto e preço atualizados para o Google e para assistentes de IA, e mantém o painel de pedidos sempre em dia.
-
Login, carrinho e troca de status de pedido ficam mais simples de programar com as ferramentas novas dessa tecnologia.
-
Essa não é a única opção. Para uma loja simples, sem login nem painel, existem tecnologias mais leves e mais baratas de manter.
Cursos para aprofundar
Formações gratuitas do portal de educação de Alexandre Caramaschi, fundador da Brasil GEO.
SEO Técnico para E-commerce
Core Web Vitals, renderização e crawl budget: o que a stack do storefront precisa entregar para ranquear e ser citada.
Curso gratuitoCRO e Experimentação
Governança de experimentos e testes A/B para evoluir a interface sem quebrar a conversão a cada release da stack.
Leve a decisão para o seu contexto
Este guia dá o mapa; a sua operação dá os números. As calculadoras do portal transformam o argumento em conta feita com os seus dados. A Onclick mostra como um backoffice integrado sustenta a decisão no dia a dia.