Pular para o conteúdo
Operating Intelligence 11 min de leitura

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.

AC

Alexandre Caramaschi

Founder da Brasil GEO, ex-CMO da Semantix (Nasdaq), cofundador da AI Brasil

Atualizado em 16 de junho de 2026

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

Conta do clienteLogin e área logada.
Carrinho persistenteSobrevive entre sessões.
Painel de pedidosDados que mudam a cada requisição.
Dashboard de estoqueConsultado pelo operador o dia inteiro.

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

  1. 1
    Turbopack como bundler padrãoEstável para todos os apps, com opt-out por --webpack.
  2. 2
    App Router com React Server ComponentsRenderização no servidor por padrão.
  3. 3
    Cache ComponentsDiretiva "use cache" para a casca estática.
  4. 4
    params 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

paramsSegmentos dinâmicos da rota.
searchParamsFiltros lidos da URL.
cookies()Sessão e preferências.
headers()Cabeçalhos da requisição.
draftMode()Modo de rascunho.
Fonte: 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, 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

12345Navegador ou crawlerServer ComponentCatálogo
Pede categoria=aneis na URLA query string carrega o filtro.
await searchParamsExtrai a categoria depois do await.
buscarProdutos({ categoria })Banco ou API headless, sem expor chave ao navegador.
Devolve HTML prontoA grade já vem filtrada.
Lista completa na primeira respostaSem depender de JavaScript no cliente.
// 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

  1. O formulário envia e-mail e senhaaction={formAction}, sem fetch manual.
  2. entrar() roda no servidorMarcada com "use server".
  3. validarCredenciais decideSem cliente, devolve { erro } para o formulário.
  4. criarSessao(cliente.id)A sessão nasce no servidor.
  5. 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

  1. 1
    CascaPainel com "use cache"Cabeçalho, navegação e moldura como HTML estático, com chave gerada pelo compilador.
  2. 2
    Suspense com fallbackMostra "Carregando pedidos..." enquanto a tabela chega.
  3. 3
    TabelaPedidos dinâmicaRenderiza via streaming, muda toda hora.
  4. 4
    Server 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.

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

revalidateTag(tag, perfil)Exige um perfil de cacheLife como segundo argumento e habilita stale-while-revalidate.
updateTag(tag)Só em Server Actions, com semântica read-your-writes: expira e relê na mesma requisição.
refresh()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.

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ão
    entãoRedireciona para /login antes de qualquer renderização
  • SeRota protegida com cookie de sessão
    entã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

  1. Clique chama aoAdicionarDentro de startTransition.
  2. addOtimista(produto) reage na horaO contador sobe no mesmo instante.
  3. adicionarItem(sku) confirma no servidorA Server Action valida e grava.
  4. 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.

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 completo
    entãocacheComponents: true; sem a flag, tudo é dinâmico em tempo de requisição
  • SeDashboard com tabelas grandes e muitas re-renderizações
    entãoreactCompiler: true, depois medir e comparar
  • SePágina simples
    entã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

View TransitionsTransições entre telas.
useEffectEvent()Novo gancho de efeito.
ActivityNovo componente.
Metadados nativostitle e meta dentro de componentes, útil para SEO e GEO de fichas.

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

  1. Ligar cacheComponentsLibera "use cache" e o Partial Prerendering.
  2. Mover params e searchParams para awaitToda leitura vira assíncrona.
  3. Trocar middleware.ts por proxy.tsSessão validada no runtime Node.
  4. 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 JavaScript
    entãoAstro islands para storefront e conteúdo
  • SeStorefront reativo e leve com runtime mínimo
    entãoSvelte 5 e SvelteKit
  • SeLogin, carrinho persistente, dashboards e dados por requisição
    entã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 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 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.

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.

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.

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

  1. 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.

  2. 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.

  3. 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.

  4. Login, carrinho e troca de status de pedido ficam mais simples de programar com as ferramentas novas dessa tecnologia.

  5. Essa não é a única opção. Para uma loja simples, sem login nem painel, existem tecnologias mais leves e mais baratas de manter.

Formações gratuitas do portal de educação de Alexandre Caramaschi, fundador da Brasil GEO.

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.

Conteúdo curado por Alexandre Caramaschi, Founder da Brasil GEO, ex-CMO da Semantix (Nasdaq), cofundador da AI Brasil. Parte do portal GEO-Ecommerce, a serviço da operação Onclick.