Um template pronto para produção em Next.js com autenticação (Clerk), banco de dados (PostgreSQL + Prisma), billing e sistema de créditos. Inclui UI com Radix + Tailwind, TypeScript e estrutura organizada para acelerar entregas.
Template mantido pela AI Coders Academy — saiba mais em
https://www.aicoders.academy/. Suporte:[email protected].
# 1) Clonar e instalar
git clone <your-repo-url>
cd nextjs-saas-template
npm install
# 2) Variáveis de ambiente
cp .env.example .env
# Edite .env (Clerk, DATABASE_URL, chaves de AI/Stripe se necessário)
# 3) Banco de dados (dev)
npm run db:push
# 4) Rodar o app
npm run dev
# Acesse http://localhost:3000Para visão geral completa, leia: docs/README.md
- 🔐 Autenticação: Clerk com rotas protegidas e middleware.
- 💾 Banco: PostgreSQL + Prisma ORM, modelos prontos (usuários, créditos, billing).
- 💳 Pagamentos: pronto para integração com Stripe e webhooks.
- 🪙 Créditos: rastreamento/consumo de créditos por operação.
- 🤖 AI Chat: integração com Vercel AI SDK usando OpenRouter (streaming e seleção de modelos).
- 📎 Anexos no Chat: upload de arquivos para Vercel Blob e anexos clicáveis na conversa.
- 🎨 UI: Radix UI + Tailwind CSS.
- 🔒 Type-safe: TypeScript do frontend ao backend.
- Node.js 18+
- Banco PostgreSQL
- Conta no Clerk (obrigatório)
- (Opcional) Conta no Stripe
- Clonar o repositório:
git clone <your-repo-url>
cd nextjs-saas-template- Instalar dependências:
npm install- Variáveis de ambiente:
cp .env.example .env- Editar
.env:- Clerk:
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,CLERK_SECRET_KEY(eCLERK_WEBHOOK_SECRETse usar webhooks) - Banco:
DATABASE_URL - App:
NEXT_PUBLIC_APP_URL - Analytics (opcional):
NEXT_PUBLIC_GTM_ID,NEXT_PUBLIC_GA_ID,NEXT_PUBLIC_FACEBOOK_PIXEL_ID - Stripe (opcional):
STRIPE_SECRET_KEY,STRIPE_WEBHOOK_SECRET,NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY - AI (opcional):
OPENROUTER_API_KEY - Uploads (opcional):
BLOB_READ_WRITE_TOKEN(Vercel Blob)
- Clerk:
- Preparar o banco (dev):
npm run db:push- Iniciar o servidor de desenvolvimento:
npm run devAcesse http://localhost:3000.
- Índice central: docs/README.md
- Arquitetura: docs/architecture.md
- Backend & API: docs/backend.md, docs/api.md
- Frontend: docs/frontend.md, docs/components.md
- Autenticação: docs/authentication.md
- Banco de Dados (Prisma): docs/database.md
- Créditos: docs/credits.md
- Diretrizes de desenvolvimento: docs/development-guidelines.md
Guias de agentes (prompts e checklists): agents/README.md
- Rota de API:
POST /api/ai/chatusaai(Vercel AI SDK) comstreamTextpara respostas via SSE. - Provedor suportado: OpenRouter (compatível com OpenAI via
baseURL). - Página protegida:
/ai-chatcom seletor de provedor/modelo e chat com streaming. - Geração de imagem (OpenRouter): alternar “Modo: Imagem” na página; envia para
POST /api/ai/imagee retorna imagens como data URLs. - Anexos: botão de clipe para enviar arquivo ao Vercel Blob e inserir links nos prompts.
Créditos
- Custos por feature e créditos por plano são configuráveis pelo admin:
- Página:
/admin/settings(abas: custos e planos/mensalidade de créditos) - API pública:
GET /api/credits/settingspara a UI - Custos padrão:
ai_text_chateai_image_generation(sobrepostos por overrides)
- Página:
- Créditos por plano: defina
Freee mapeie IDs do Clerk (cplan_*) para{ name, credits }no Admin (persistidos emAdminSettings) - Enforced no backend via
validateCreditsForFeature/deductCreditsForFeaturee integrados na UI viauseCredits().getCost()/canPerformOperation(). - Reembolso automático:
- Chat: se a chamada ao provedor falhar após o débito, o sistema reembolsa e retorna 502
- Imagem: reembolsa em respostas inválidas/erros ou sem imagens
Configuração
- Defina
OPENROUTER_API_KEYno.env. - Abra
/ai-chat, selecione provedor/modelo e envie mensagens.
Extensão
- Para adicionar modelos estáticos, edite
MODELSemsrc/app/(protected)/ai-chat/page.tsx(modelos dinâmicos vêm da API do OpenRouter).
- API:
POST /api/upload(multipart/form-data com campofile). Requer sessão (Clerk). - Armazena em
uploads/<clerkUserId>/<timestamp>-<arquivo>no Blob Store vinculado ao token. - Retorna
{ url, pathname, contentType, size, name }. - Habilitar: defina
BLOB_READ_WRITE_TOKENem.env. O token já inclui Store/Região; não é preciso configurar no código. - Base URL padrão:
https://blob.vercel-storage.com(ou domínio customizado se configurado no Vercel Blob). - Detalhes: veja
docs/uploads.md.
src/
├── app/
│ ├── (public)/ # Rotas públicas (landing, auth)
│ ├── (protected)/ # Rotas protegidas (dashboard, billing)
│ └── api/ # API Routes (App Router)
├── components/
│ ├── ui/ # Componentes de UI reutilizáveis
│ └── credits/ # Componentes do sistema de créditos
├── hooks/ # Hooks React personalizados
└── lib/ # Auth, DB, utilidades, domínios e queries
└── queries/ # Camada de acesso a dados (DAL) para uso em Server Components
- Configuração central da marca:
src/lib/brand-config.ts- Nome, descrição, palavras-chave, URL pública, logos/ícones e imagem OG
- IDs de analytics/pixels (GTM, GA4, Meta Pixel)
- Usos:
- Metadados globais em
src/app/layout.tsx - Header/Footer públicos
- Injeção de pixels via
AnalyticsPixels
- Metadados globais em
- Guia: veja
docs/brand-config.md
- Páginas de login/cadastro com Clerk
- Rotas protegidas via middleware (
src/middleware.ts)
- Prisma ORM + PostgreSQL
- Modelos para usuários e créditos
- Nunca importe o Prisma Client (
@/lib/db) em Client Components ou no browser. - Server Components não devem executar queries diretamente via Prisma. Em vez disso, consuma funções da camada de queries em
src/lib/queries/*.- Exemplo:
getActivePlansSorted()emsrc/lib/queries/plans.tsusado porsrc/app/(public)/page.tsx.
- Exemplo:
- API Routes (
src/app/api/*) e Server Actions podem usar Prisma diretamente ou reutilizar funções da camada de queries.
- Utils de validação/uso em
src/lib/credits/* - Páginas de assinatura e handlers de webhook
- A UI lê o saldo sempre do backend:
GET /api/credits/me. useCredits()usa React Query para buscar e manter atualizado (30s e ao focar a janela).- Após consumir créditos (ex.: chat ou imagem), a UI chama
refresh()para refetch imediato. - Hook:
src/hooks/use-credits.tsexpõe{ credits, isLoading, canPerformOperation, getCost, refresh }e lêGET /api/credits/settingspara custos dinâmicos. - Health check de enums/Prisma:
GET /api/admin/health/credits-enum(somente admin) valida o mapeamentoFeature → OperationType.
- Para crédito avulso (fora da assinatura), mapeie os
Stripe Price IDspara a quantidade de créditos emsrc/lib/clerk/credit-packs.ts.- Exemplo:
export const CREDIT_PACK_PRICE_TO_CREDITS = { 'price_small_pack': 100, 'price_medium_pack': 500, }
- Exemplo:
- O webhook do Clerk em
src/app/api/webhooks/clerk/route.tssoma os créditos correspondentes quando recebeinvoice.payment_succeededcontendo esses prices. - Preencha seus Price IDs reais no arquivo acima. Se o pagamento for apenas renovação de assinatura, os créditos são atualizados pelos eventos
subscription.updated.
- Defaults:
src/lib/credits/feature-config.ts(mapeadas paraOperationType) - Overrides persistentes (admin):
AdminSettings+ helpers emsrc/lib/credits/settings.tsgetFeatureCost(feature)egetPlanCredits(planId)retornam valores efetivos
- Validação e desconto transacional:
import { validateCreditsForFeature, deductCreditsForFeature } from '@/lib/credits/deduct'
import { type FeatureKey } from '@/lib/credits/feature-config'
const feature: FeatureKey = 'ai_text_chat'
await validateCreditsForFeature(clerkUserId, feature)
await deductCreditsForFeature({
clerkUserId,
feature,
projectId,
details: { tasks: 3 },
quantity: 3,
})Observação: ajuste FEATURE_CREDIT_COSTS como base e/ou use /admin/settings para overrides.
- No Admin (
/admin/settings), mapeie seus IDs de planos do Clerk (cplan_*) para umname(exibição interna) ecredits(mensais). - Não há plano gratuito fixo no código. Se houver, crie-o no Clerk e mapeie o ID do plano com nome e créditos no Admin.
- O webhook em
src/app/api/webhooks/clerk/route.tsusagetPlanCredits(planId)passando osubscription.plan_idpara atualizar o saldo mensal.
Importação de Planos (Clerk)
- No Admin → Settings (aba Planos), você pode:
- Detectar planos via hook do Clerk (quando disponível) e adicioná-los rapidamente
- Tentar importar via API do backend (
GET /api/admin/clerk/plans, requerCLERK_SECRET_KEY) - Colar um JSON com a lista de planos e nomes
- Acesso:
/admin(SSR guard + middleware). Configure.env:ADMIN_EMAILSouADMIN_USER_IDS. - Funcionalidades:
- Usuários: listar, ajustar créditos, excluir
- Convidar usuário por e‑mail (via Clerk) com toasts
- Convites pendentes: visualizar, reenviar e revogar
- Sincronizar usuários do Clerk → DB (backup caso webhook falhe)
- Requisitos Clerk para convites:
- Invitations e envio de e‑mails habilitados no projeto Clerk
- Redirect permitido:
${NEXT_PUBLIC_APP_URL}/sign-up
- APIs Admin relevantes:
POST /api/admin/users/inviteGET /api/admin/users/invitationsPOST /api/admin/users/invitations/:id/resendPOST /api/admin/users/invitations/:id/revokePOST /api/admin/users/sync({ pageSize?, maxPages? })GET /api/admin/health/credits-enumPUT /api/admin/credits/:id(ajuste por saldo)PUT /api/admin/users/:id/credits(ajuste via ID do usuário)DELETE /api/admin/users/:id(desativa o usuário – soft delete)POST /api/admin/users/:id/activate(reativa o usuário)
Notas Prisma
- O Prisma Client é gerado em
prisma/generated/client. - O código usa esse client gerado (não
@prisma/client) para evitar divergências de enums/tipos em runtime. - Atalho de tipos:
src/lib/prisma-types.tsreexportaOperationTypedo client gerado.
npm run dev— Dev servernpm run build— Gera Prisma Client e build de produçãonpm start— Servidor em produçãonpm run lint— Lint do Next/TypeScriptnpm run typecheck— Verificação de tiposnpm run db:push— Sincroniza schema (dev)npm run db:migrate— Migrações Prismanpm run db:studio— Prisma Studio
Pronto para Vercel/Netlify/Node. Na Vercel:
- Configure variáveis de
.env.example(Clerk,DATABASE_URL, Stripe, etc.) - Use runtime Node (não Edge) para endpoints com Prisma
- Aponte webhooks (Clerk/Stripe) para rotas em
src/app/api/webhooks/*
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEYCLERK_SECRET_KEYDATABASE_URL- Outras de
.env.example, conforme necessário
Use estes guias/prompts ao iniciar tarefas ou revisões:
AGENTS.md— Diretrizes do Repositório (estrutura, scripts, estilo, PRs)agents/README.md— Índice dos Guiasagents/security-check.md— Verificação de Segurançaagents/frontend-development.md— Desenvolvimento Frontendagents/backend-development.md— Desenvolvimento Backendagents/database-development.md— Banco de Dadosagents/architecture-planning.md— Arquitetura & Planejamento
Guia detalhado para Clerk, banco, deploy na Vercel e uso de agentes.
- Clerk:
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY(pública)CLERK_SECRET_KEY(secreta)CLERK_WEBHOOK_SECRET(webhooks)
- URLs do Clerk (padrões do template):
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-inNEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-upNEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboardNEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/dashboard
- Banco:
DATABASE_URL=postgresql://user:password@host:5432/saas_template - App:
NEXT_PUBLIC_APP_URL=http://localhost:3000 - Stripe (opcional):
STRIPE_SECRET_KEY,STRIPE_WEBHOOK_SECRET,NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
- Crie um app em dashboard.clerk.com e copie as chaves.
- Defina
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEYeCLERK_SECRET_KEYem.env. - Redirects/origens autorizadas:
- Dev:
http://localhost:3000 - Produção: domínio
.vercel.appe custom domain
- Dev:
- Rotas de auth:
src/app/(public)/sign-inesrc/app/(public)/sign-up. Rotas protegidas:src/app/(protected). - Webhooks (opcional): configure endpoint e
CLERK_WEBHOOK_SECRET.
- Defina ao menos um admin via variáveis de ambiente:
[email protected],[email protected]- ou
ADMIN_USER_IDS=usr_123,usr_456(IDs do Clerk)
- Acesse o painel:
/admin(somente admins conseguem entrar). As APIs emsrc/app/api/admin/*validam admin no servidor.
Recomendado para desenvolvimento local. Requer Docker instalado e em execução.
Comando padrão:
npm run db:dockerO script scripts/setup-postgres-docker.mjs:
- Cria (se necessário) um volume Docker para persistir dados
- Sobe um PostgreSQL em um container nomeado
- Mapeia a porta local escolhida e imprime a
DATABASE_URLpronta para colar no.env
Personalização via variáveis de ambiente:
PG_CONTAINER_NAME(padrão:saas-postgres)PG_DB(padrão:saas_template)PG_USER(padrão:postgres)PG_PASSWORD(padrão:postgres)PG_PORT(padrão:5432)PG_IMAGE(padrão:postgres:16)PG_VOLUME(padrão:saas_postgres_data)
Exemplos:
# Porta alternativa e credenciais próprias
PG_PORT=5433 PG_DB=app PG_USER=app PG_PASSWORD=secret npm run db:docker
# Alterar nome do container e volume
PG_CONTAINER_NAME=my-db PG_VOLUME=my_db_volume npm run db:dockerComandos úteis do Docker (após criar o container):
docker stop saas-postgres # parar
docker start saas-postgres # iniciar
docker logs -f saas-postgres # logsDefina a DATABASE_URL no .env com a URL impressa pelo script, por exemplo:
DATABASE_URL="postgresql://app:secret@localhost:5433/app"Depois, rode:
npm run db:push # ambiente de desenvolvimento
# ou
npm run db:migrate # migrações versionadasOpção A — Docker local (manual):
docker run --name saas-postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_USER=postgres -e POSTGRES_DB=saas_template -p 5432:5432 -d postgres:16
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/saas_template
Opção B — Gerenciado (Neon, Supabase, RDS): crie a base e copie a URL.
Sincronizar/Migrar:
- Rápido (dev):
npm run db:push - Versionado (recomendado):
npm run db:migrate - Inspecionar:
npm run db:studio
Observações:
npm run devenpm run buildexecutamprisma generateautomaticamente.- O Prisma Client é gerado em
prisma/generated/cliente não é versionado (gitignored). Se faltar, rodenpx prisma generate. - Mantenha
userId/workspaceIdem modelos multi-tenant.
npm install- Configure
.env - Inicie banco (
db:pushoudb:migrate) npm run dev→ http://localhost:3000- Valide sign-in/sign-up e acesso às rotas protegidas
Para integrar o Clerk localmente, você precisa expor seu localhost através de um túnel público. Sem um endpoint público, o Clerk não consegue entregar as chamadas do webhook ao seu ambiente de desenvolvimento.
Passos rápidos
- Inicie o app:
npm run dev - Inicie um túnel (escolha uma opção):
- Vercel Dev Tunnel (se suportado pelo seu CLI):
npm run dev:tunnel - Cloudflare Tunnel (recomendado):
npm run tunnel:cf(requercloudflaredinstalado) - ngrok (alternativa):
npm run tunnel:ngrok(requerngrokinstalado)
- Vercel Dev Tunnel (se suportado pelo seu CLI):
- No Clerk → Webhooks → Add endpoint
- URL:
https://<URL-DO-TUNEL>/api/webhooks/clerk - Copie o “Signing secret” e adicione em
.env:CLERK_WEBHOOK_SECRET=whsec_...
- URL:
- Envie um “Test event” no Clerk para validar.
Notas
- A rota do webhook está em
src/app/api/webhooks/clerk/route.tse valida as assinaturas Svix. - Algumas versões do Vercel CLI não suportam
vercel dev --tunnel. Use Cloudflare/ngrok se o túnel do Vercel não estiver disponível. - Guia completo: veja
docs/dev-webhooks.md.
Requisitos das ferramentas de túnel
- Cloudflare Tunnel: instalar
cloudflared(ex.: macOSbrew install cloudflare/cloudflare/cloudflared). - ngrok: instalar e autenticar
ngrok(ex.:brew install ngrok/ngrok/ngrokengrok config add-authtoken <TOKEN>).
- Admin detalhado:
docs/admin.md - Créditos e sincronização:
docs/credits.md - Uploads de arquivos:
docs/uploads.md
- Importe o repositório
- Configure variáveis de ambiente (Clerk,
DATABASE_URL, Stripe) - Build & Runtime:
- Build padrão do Next (gera Prisma Client)
- Runtime Node (não Edge) para Prisma
- Banco: use provedor acessível pela Vercel (Neon/Supabase); habilite pooling se necessário
- Clerk produção: adicione domínios
.vercel.appe customizados - Webhooks: aponte Stripe/Clerk para
src/app/api/webhooks/*e defina*_WEBHOOK_SECRET - Pós-deploy: teste auth, rotas protegidas, acesso ao DB e créditos
- Leia
AGENTS.mdeagents/README.md - Copie o prompt do arquivo em
agents/pertinente e inclua contexto (arquivos/rotas/contratos) - Anexe o guia do agente na descrição do PR
- Para recursos que consomem créditos, use as keys:
ai_text_chateai_image_generation(custos emsrc/lib/credits/feature-config.ts)
- Prisma em produção: use runtime Node e confirme
prisma generateno build - Login falha no deploy: verifique domínios/redirects no Clerk e variáveis na Vercel
DATABASE_URLinválida: teste conexão localmente; confirme SSL/Pooling no provedor- Tipos/ESLint: execute
npm run typecheckenpm run lintantes do PR
MIT