Aplicação Next.js 16 com App Router e Cache Components: a vitrine e o blog são pré-renderizados com "use cache" e cacheTag, e o que depende da requisição (menu do usuário, filtros, carrinho) faz streaming dentro de Suspense. Toda escrita passa por Server Actions validadas com Zod, que acessam o Postgres do Supabase diretamente via Drizzle ORM; a Data API do Supabase fica fechada por RLS sem policies. O login é feito pelo Firebase Auth (e-mail/senha e Google) e trocado por um cookie de sessão httpOnly no servidor, com o papel de administrador num custom claim. O carrinho vive no navegador (Zustand + localStorage), mas preço, estoque e frete são sempre recalculados no servidor no fechamento do pedido.
- Front-end
- Next.js 16.3 (App Router, Cache Components, Partial Prefetching, typed routes)React 19.2Tailwind CSS 4shadcn/ui (Radix)Zustand 5
- Back-end
- Server Actions e Route HandlersZod 4 para validaçãoDrizzle ORM 0.45 com o driver postgres.js
- Banco e arquivos
- Supabase Postgres (7 migrações versionadas com drizzle-kit) e Supabase Storage para as fotos
- Autenticação
- Firebase Auth (cliente) + Firebase Admin SDK (cookie de sessão e custom claims)
- Integrações
- WhatsApp (link com mensagem pronta)Cloudflare TurnstileResend (e-mail de novo pedido)Mercado Pago e Focus NFe (ligados por variável de ambiente)
- Infra e qualidade
- Vercel e Vercel AnalyticsPWA com service worker próprioTypeScript strict e ESLint
Segurança
Preço conferido no servidor
O carrinho do navegador nunca é fonte de verdade: a Server Action de checkout busca cada produto e variação no banco, bloqueia o pedido se algo saiu do catálogo ou se o estoque não cobre a quantidade, e recalcula desconto do Pix e frete com as mesmas funções puras que a vitrine usa para exibir o preço. O código curto do pedido é UNIQUE no banco e o insert usa ON CONFLICT DO NOTHING com nova tentativa em caso de colisão.
Dados
Banco fechado para a API pública
Todas as tabelas têm RLS ligado e nenhuma policy, então a chave publicável do Supabase não lê nem escreve nada; só o servidor Next.js, conectado direto ao Postgres, acessa os dados. O bucket de imagens é de leitura pública, mas aceita apenas PNG, JPEG e WebP até 5 MB (SVG fica de fora por poder carregar script), e o upload só acontece por URL assinada gerada depois de verificar o administrador.
Arquitetura
Autorização junto ao dado, não só no proxy
O proxy.ts do Next 16 faz apenas uma checagem otimista da existência do cookie, sem chamada de rede. A verificação real fica em requireAdmin()/assertAdmin(), chamadas em cada página e Server Action do painel, porque Server Actions são endpoints POST públicos. O papel de administrador é um custom claim assinado pelo Firebase, e a verificação do cookie de sessão usa checkRevoked para derrubar sessões revogadas.
Performance
Vitrine em cache com revalidação por tag
Com cacheComponents ligado, as leituras públicas usam "use cache" e cacheTag; quando o lojista salva um produto ou post, a Server Action chama revalidateTag() e só aquele conteúdo é reconstruído. Páginas de produto não são geradas no build: com partialPrefetching, a primeira visita recebe o App Shell na hora e a página completa entra em cache em seguida.
Segurança
Webhook de pagamento que não confia no corpo
A integração com o Mercado Pago valida o header x-signature, reconsulta o pagamento na API antes de marcar qualquer coisa, confere se o valor bate com o total gravado no pedido e é idempotente para lidar com reenvios. A baixa de estoque roda numa transação condicionada a uma flag no pedido, então não é aplicada duas vezes.
SEO
SEO local e conteúdo para IA
Dados estruturados schema.org (OnlineStore com endereço e área atendida, Product, BlogPosting e WebSite com SearchAction), sitemap dinâmico, robots, imagem Open Graph gerada no servidor e canonical por página. A loja também publica /llms.txt e /llms-full.txt, gerados a partir do catálogo em cache e marcados com X-Robots-Tag: noindex.