Apêndice: Decisões de stack
- Mapa das alternativas de stack que o Módulo 3 não explorou
- Identificação de qual sub-seção se aplica ao seu projeto
O Módulo 3 assume a stack default do starter: Next.js App Router + Postgres + Drizzle + Zod + Route Handlers. Funcionou. Mas seu projeto pode ter escolhido outra coisa: Supabase em vez de Postgres puro, Server Actions em vez de Route Handlers, Prisma em vez de Drizzle. Este apêndice é o mapa.
Cada sub-seção tem 3 partes: o que muda no Módulo 3 (quais passos precisam de adaptação), o que não muda (princípios universais) e decisão registrada (se você escolheu essa via, comita uma linha no docs/decisions.md do projeto).
O apêndice não está aqui pra te converter. Se você já tomou a decisão, beleza: aqui está o que muda.
Drizzle vs Prisma
O que muda:
- L02 (schema): sintaxe do schema é diferente. Drizzle usa funções TypeScript (
pgTable,varchar,bigint); Prisma usa DSL própria noschema.prisma. Migration é gerada de forma diferente (drizzle-kit generatevsprisma migrate dev). - L04 (API route): sintaxe do insert. Drizzle:
db.insert(recibos).values(data).returning(). Prisma:db.recibo.create({ data }). Ambos retornam o objeto inserido. - Tratamento de erros do banco (L04): os códigos de erro do Prisma vêm como
P2002(unique constraint) eP2003(FK); o Drizzle expõe os códigos Postgres crus (23505,23503). A lógica de tradução muda, mas o resultado (409, 400) continua o mesmo.
O que não muda:
- Validação na borda com Zod (L03) — Zod não depende de ORM
- Decisão de tipos brasileiros (
bigintem centavos,varchar(11)em CPF) — vale igual - Pattern de 1 commit por tarefa, 1 PR por feature — vale igual
Decisão registrada: se você usa Prisma, comita 1 linha em docs/decisions.md: "ORM: Prisma. Razão: equipe já conhecia, ou tooling de migração mais maduro, ou outro motivo seu."
Server Actions vs Route Handlers
O que muda:
- L04 inteira. Server Actions movem o "POST /api/recibos" pra um Server Component (ou async function dentro do mesmo arquivo do form). O contrato HTTP some: você chama uma função TypeScript que roda no servidor.
- L05 (form): o form não tem
fetch('/api/recibos'); temaction={createRecibo}ondecreateReciboé a Server Action. - Testes E2E: ficam um pouco diferentes. Em vez de testar a API com
supertestou similar, você testa a Server Action chamando ela como função (Vitest/Jest com mock de DB).
O que não muda:
- Validação na borda com Zod (L03) — a Server Action ainda recebe um payload externo (FormData ou JSON) e precisa validar
- Tratamento de erros do banco (unique constraint, FK): a Server Action joga exception e o componente cliente captura via
useFormStateou try/catch - Evento rastreado (L06): a chamada
analytics.track()continua sendo feita no ponto de sucesso, só que dentro da Server Action
Decisão registrada: se você usa Server Actions, comita 1 linha: "API: Server Actions. Razão: stack mais coesa com Server Components, menos boilerplate, ou outro motivo seu."
Aviso prático: Server Actions em Next.js ainda são relativamente novas (estáveis desde o Next 14). Algumas libs (principalmente de teste e de fila) ainda têm tooling mais maduro para Route Handlers. Se você está construindo solo, qualquer um serve. Se está em equipe maior, Route Handlers tende a dar menos surpresa.
Supabase vs Postgres puro
O que muda:
- L02 (schema): você pode escrever schema no Drizzle e usar Supabase como host de Postgres (caminho recomendado se você quer escolha técnica). Ou pode usar o Studio do Supabase e escrever schema visualmente — funciona, mas perde versionamento no repo. Recomendação: schema no código.
- Conexão:
DATABASE_URLvem do dashboard Supabase. Suporta connection pooling (porta 6543) e direct connection (porta 5432). Use a 6543 em runtime; a 5432 só pra migrations. - Row Level Security (RLS): o Supabase recomenda RLS por padrão. Pra fatia 1 ainda sem auth, desabilite RLS na tabela ou crie políticas permissivas. Senão, o insert da L04 vai falhar com "permission denied for table." A L04 não cobre RLS; configura na sua tabela conforme a necessidade.
O que não muda:
- Schema, validação, API route — tudo igual a Postgres puro
- Sintaxe SQL, tipos, índices — igual
Decisão registrada: "DB: Supabase Postgres. Razão: setup zero, dashboard útil, auth integrado pra futuro."
Aviso prático: o Supabase tem auth, storage e realtime embutidos. Não use eles ainda na fatia 1. Cada um adiciona complexidade que não dá pra reverter sem esforço. Use só o Postgres por enquanto e adicione os outros quando o produto exigir.
PostHog vs Plausible (analytics)
Você não escolheu nada na L06 — só criou analytics.track() como no-op. A escolha real vem no Módulo 4. Mas se quiser antecipar a decisão, aqui está o mapa.
PostHog:
- Event-based (você manda eventos com propriedades arbitrárias)
- Free tier: 1M eventos/mês — suficiente pra fatia 1 e meses seguintes
- Suporta funil, retenção, A/B test, session replay
- Setup: precisa de SDK no client + cookie consent (LGPD)
- Pra quem é: builder que quer analytics de produto sério
Plausible:
- Page view based (eventos custom são limitados)
- Free tier: nenhum free real — $9/mês minimo
- Cookieless por design — não precisa banner de consent
- Pra quem é: builder cívico/público que quer LGPD-friendly out of the box
Decisão registrada quando escolher: "Analytics: PostHog. Razão: free tier generoso + eventos custom suficientes pro funil." Ou: "Analytics: Plausible. Razão: produto público, sem cookie banner, custo previsível."
TypeScript strict mode
O Starter já vem com strict: true no tsconfig.json. Essa decisão já está tomada: não desabilite. Toda lição do Módulo 3 assume strict ligado. Se você desabilitou, vai começar a aceitar bugs que o strict pegava (null check faltando, type assertion errada).
Decisão registrada: não precisa registrar — strict é default do Starter. Se você desabilitou, registra a razão.
Onde isso vive depois do M3
docs/decisions.md (ou decisions/, pasta com 1 arquivo por decisão maior — formato ADR) é convenção do M5. O Módulo 5 vai te ensinar a manter decisões versionadas como o CEAP faz (você viu CEAP/DECISIONS.md em várias lições do M3).
Por enquanto, uma frase commitada em docs/decisions.md já resolve: "DB: X. ORM: Y. API: Z." Se algum dia outro builder olhar o repo, vai entender as escolhas em 30 segundos.