Capítulo 01 de 35

Setup Inicial: Arquitetura Zero-to-Production no Dia 1

Índice do Manual

O "Dia 1" de um projeto SaaS define se você passará o próximo ano entregando funcionalidades ou resolvendo débitos técnicos. Se a fundação de um projeto for inicializada incorretamente, refatorar a autenticação de cookies ou inserir o modo escuro (Dark Mode) posteriormente custará centenas de horas de trabalho de engenharia.

Este manual contém a sequência linear obrigatória para realizar o Setup Inicial de qualquer projeto SaaS corporativo do zero, utilizando a stack Next.js (App Router) + Supabase + Shadcn.

[!TIP] A Ilusão da Bala de Prata: Esta stack resolve 95% dos problemas de desenvolvimento B2B moderno. Porém, se o seu escopo envolver relatórios pesados ou conexões de longa duração, não tente forçar a Vercel a fazer o impossível. Entenda quando delegar o peso nos bastidores lendo nosso artigo: O Limite da Bala de Prata: Quando o Next.js Precisa de um NestJS.

Para manter um padrão de excelência de nível corporativo trabalhando sozinho, você deve seguir rigorosamente estes 11 passos antes de criar a primeira tela.


1. A Preparação da Nuvem (No Navegador)

Antes de digitar qualquer comando no terminal, a infraestrutura nas nuvens precisa existir para receber o projeto. No seu navegador:

  1. GitHub (Controle de Versão Duplo): Crie um repositório vazio. A regra de ouro corporativa é nunca commitar direto em produção. Portanto, você deve criar e proteger duas branches principais:
    • main: Reflete o código exato que está no ar para os clientes (Produção).
    • staging: Reflete o código em fase de testes (Homologação). Todo o desenvolvimento diário deve ir para a branch staging primeiro.
    • (Configuração de Limpeza): Acesse Settings > Pull Requests no seu repositório recém-criado e ative a caixa Automatically delete head branches. Isso fará com que o GitHub jogue no lixo as branches de features automaticamente assim que o código for aprovado, mantendo a nuvem limpa.
  2. Supabase (Backend Físico Duplo): Diferente de APIs tradicionais, o Supabase provisiona bancos PostgreSQL físicos. Criar tabelas misturando dados reais e de teste é um erro fatal. Você deve acessar o painel do Supabase e criar DOIS projetos completamente novos e distintos:
    • [SeuApp]-Prod: O banco de dados intocável, com dados reais dos clientes.
    • [SeuApp]-Staging: O banco de dados onde você vai rodar testes, dropar tabelas e usar dados falsos.
    • Acesse as configurações (Project Settings > API) de ambos os projetos e anote em um bloco de notas as chaves Project URL e anon public key de cada um.
  3. Vercel (Frontend Unificado): Crie um novo projeto vazio apontando para o seu repositório no GitHub. Acesse a aba Settings > Environment Variables. O segredo da arquitetura Multi-Ambiente é criar chaves com o mesmo nome, mas valores diferentes dependendo do contexto. Faça o seguinte mapeamento:
    • Crie a variável NEXT_PUBLIC_SUPABASE_URL e cole a URL do seu Supabase Prod. Logo abaixo, desmarque todas as caixas e deixe apenas a opção "Production" marcada. Salve.
    • Crie novamente a mesma variável NEXT_PUBLIC_SUPABASE_URL, mas agora cole a URL do seu Supabase Staging. Desmarque as outras opções e deixe apenas "Preview" marcado (você pode amarrar essa variável especificamente à sua branch staging selecionando o Custom Branch).
    • Repita esse processo duplo (uma para Prod, uma para Preview) para a NEXT_PUBLIC_SUPABASE_ANON_KEY e qualquer outra chave secreta.

[!WARNING] O Ponto Cego: Integrações Externas (Stripe, E-mails) Se o sistema de Staging simular uma "Aprovação de Compra" e disparar um e-mail para um cliente real, o estrago jurídico será colossal. Para blindar integrações, repita o processo duplo acima para todas as ferramentas externas. Insira as chaves reais da Stripe e do Resend apenas no ambiente Production. Insira as chaves de teste (sk_test_...) no Preview (Staging). Assim, o cliente testa o fluxo de pagamento inteiro no cartão clonado da Stripe, o e-mail cai num buraco negro seguro, e a arquitetura garante 100% de paz mental para a engenharia.


2. Criação da Pasta e do Next.js (O Núcleo)

Inicialize o projeto com as configurações mais restritas e modernas, garantindo que ele será criado dentro da pasta correta.

Comandos (Execute um por um):

# 1. Crie a pasta do cliente/projeto e entre nela
mkdir meu-novo-saas
cd meu-novo-saas

# 2. Instale o Next.js dentro desta pasta
npx create-next-app@latest ./ \
  --typescript \
  --tailwind \
  --eslint \
  --app \
  --src-dir \
  --import-alias "@/*" \
  --use-npm
  • Por que --src-dir? O código da aplicação NUNCA deve se misturar com os arquivos de configuração raiz.

3. A Malha Multi-Contas (direnv)

Se você desenvolve projetos para múltiplos clientes simultaneamente, a regra de ouro arquitetural é nunca fazer login global (supabase login ou vercel login) na sua máquina.

Agora que você já criou e entrou na pasta do projeto, você DEVE criar o arquivo .envrc na raiz desta pasta com os tokens do cliente, conforme ensinado no manual de Múltiplas Contas.

Após criar o arquivo, ative a pasta no terminal rodando:

direnv allow

4. O Boilerplate Crítico: Supabase SSR

O Next.js App Router renderiza o HTML no servidor. O SDK antigo do Supabase (@supabase/supabase-js) não consegue ler cookies de sessão dentro de Server Components. Se você ignorar este passo, seu sistema de autenticação falhará.

Passo a passo:

  1. Instale o pacote oficial de Server-Side Rendering:
npm install @supabase/ssr @supabase/supabase-js
  1. Crie imediatamente a pasta utilitária de conexão e seus 3 arquivos sagrados, copiando o código oficial da documentação do Supabase:
    • src/utils/supabase/server.ts (Uso exclusivo em Server Components, Server Actions e API Routes).
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'

export async function createClient() {
  const cookieStore = await cookies()
  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() { return cookieStore.getAll() },
        setAll(keysToSet) {
          try {
            keysToSet.forEach(({ name, value, options }) => cookieStore.set(name, value, options))
          } catch { }
        },
      },
    }
  )
}
  • src/utils/supabase/client.ts (Uso exclusivo em Client Components com 'use client').
import { createBrowserClient } from '@supabase/ssr'

export function createClient() {
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  )
}
  • src/utils/supabase/middleware.ts (Obrigatório para manter as sessões vivas).
import { createServerClient } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'

export async function updateSession(request: NextRequest) {
  let supabaseResponse = NextResponse.next({ request })
  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() { return request.cookies.getAll() },
        setAll(keysToSet) {
          keysToSet.forEach(({ name, value, options }) => request.cookies.set(name, value))
          supabaseResponse = NextResponse.next({ request })
          keysToSet.forEach(({ name, value, options }) => supabaseResponse.cookies.set(name, value, options))
        },
      },
    }
  )
  await supabase.auth.getUser()
  return supabaseResponse
}
  • src/middleware.ts (Na raiz do projeto, aciona o utilitário).
import { type NextRequest } from 'next/server'
import { updateSession } from '@/utils/supabase/middleware'

export async function middleware(request: NextRequest) {
  return await updateSession(request)
}

export const config = {
  matcher: [
    '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
  ],
}

5. Estilização e Dark Mode Nativo

Adicionar o Tailwind e o Shadcn não é suficiente. Um SaaS corporativo deve ter o modo escuro funcionando no primeiro minuto. Tentar instalar Dark Mode após criar 30 painéis destruirá seu layout.

Passo a passo:

  1. Inicialize o Shadcn UI:
npx shadcn@latest init

(Escolha "New York", ative as variáveis CSS e defina a cor primária apropriada). 2. Instale imediatamente o provedor de tema:

npm install next-themes
  1. Crie o arquivo src/components/theme-provider.tsx:
"use client"
import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"
import { type ThemeProviderProps } from "next-themes/dist/types"

export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
  return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}
  1. Envolva toda a aplicação no arquivo src/app/layout.tsx ativando as fontes modernas (Inter ou Geist).
import { Inter } from "next/font/google"
import { ThemeProvider } from "@/components/theme-provider"

const inter = Inter({ subsets: ["latin"] })

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="pt-BR" suppressHydrationWarning>
      <body className={`${inter.className} antialiased`}>
        <ThemeProvider 
          attribute="class" 
          defaultTheme="dark" 
          enableSystem 
          disableTransitionOnChange
        >
          {children}
        </ThemeProvider>
      </body>
    </html>
  )
}

[!TIP] A Psicologia do Design Corporativo: O estilo "New York" do Shadcn garante densidade de dados (tabelas e botões compactos) imitando interfaces premium como Vercel e Stripe. Já o uso de fontes como Inter ou Geist elimina a aparência amadora das fontes de navegador, transmitindo uma sensação imediata de "Software Rápido e Seguro" para o usuário.


6. Permissão de Imagens Remotas (Next Config)

Quando o usuário fizer upload de um avatar para o Supabase Storage (Bucket S3), o Next.js vai bloquear o carregamento da imagem nativamente por questões de segurança (se você usar a tag <Image />).

Resolução: Edite o arquivo raiz next.config.ts no Dia 1, adicionando a configuração de images apontando exatamente para o domínio do seu bucket.

(Onde encontro este ID? O ID_DO_SEU_PROJETO é o Reference ID de 20 letras que compõe a sua URL do Supabase, por exemplo: se a sua URL for https://xyz123.supabase.co, o seu ID é xyz123).

Copie e cole a estrutura abaixo substituindo o ID pelo do seu projeto de Produção:

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: "https",
        hostname: "ID_DO_SEU_PROJETO.supabase.co",
        port: "",
        pathname: "/storage/v1/object/public/**",
      },
    ],
  },
};

export default nextConfig;

7. Conexão Local e Banco de Tipos Fortes

Não programe "às cegas". A interface precisa conhecer perfeitamente a estrutura do banco de dados na nuvem.

Passo a passo:

  1. Inicialize o ambiente Supabase Local no projeto:
npx supabase init
  1. Obrigatório: Como você já criou e ativou o seu .envrc no Passo 3, você não precisa fazer login.
  2. A regra de ouro (Staging-First): Linke o repositório local ao seu projeto de Staging na nuvem (NUNCA à produção neste momento):
npx supabase link --project-ref ID_DO_PROJETO_STAGING

(Por que Staging e não Local? O "local" é a própria pasta onde você está. O comando link serve para avisar à CLI qual é o destino remoto oficial dessa pasta. Quando você rodar supabase db push no futuro, ele saberá que deve enviar as alterações para a nuvem de Staging. O direnv que você configurou no Passo 3 serve apenas para te autenticar).

  1. Adicione o seguinte script obrigatório no package.json:
    "scripts": {
      "gen-types": "supabase gen types typescript --local > src/types/database.types.ts"
    }
    
    Sempre que criar uma tabela no banco, execute npm run gen-types para injetar o tipo TypeScript no frontend.

8. Vercel Env Sync (Baixando Variáveis)

Chega de copiar e colar chaves secretas e errar.

  1. Acesse o painel da Vercel (Settings > Environment Variables). A Vercel possui três ambientes fundamentais (Production, Preview e Development). Você deve espelhar a arquitetura do Supabase aqui:
    • Chaves de Produção: Insira as chaves reais (Supabase Prod, Stripe Live, Resend Live) e deixe apenas o checkbox "Production" marcado.
    • Chaves de Staging (Homologação): Insira as chaves do seu banco de testes (Supabase Staging, Stripe Test) e deixe apenas o checkbox "Preview" marcado (Dica: Logo abaixo do checkbox Preview, clique em "All Preview Branches", mude para "Select Custom Branch" e digite staging para que o painel da Vercel isole essas chaves apenas nessa branch).
    • Chaves de Development (Local): Marque também o checkbox "Development" junto com o Preview para que as mesmas chaves de teste sejam baixadas para a máquina dos desenvolvedores quando rodarem o Vercel CLI localmente.
  2. Obrigatório: Abra o seu arquivo .envrc (criado no Passo 3) e adicione a nova linha export VERCEL_TOKEN="sua_chave_vercel", rodando direnv allow no terminal em seguida para carregar.
  3. No terminal local, linke o projeto:
vercel link
  1. Baixe as chaves do ambiente de Staging (Preview) para a sua máquina:
vercel env pull .env.local --environment=preview
  1. Ligando o Banco Local: Para realizar o sequestro local das variáveis na próxima etapa, você precisa saber quais são as chaves da sua máquina. Abra uma nova aba no terminal e rode:
npx supabase start

(O terminal imprimirá a API URL e a anon key. Copie ambas).

  1. Sequestro Local (Crítico): Abra o arquivo .env.local que acabou de ser baixado pela Vercel. Substitua a NEXT_PUBLIC_SUPABASE_URL e a NEXT_PUBLIC_SUPABASE_ANON_KEY da nuvem (Staging) pelos valores que você acabou de copiar do seu banco local. Se você pular isso, seu ambiente de desenvolvimento salvará dados diretamente no banco de Staging da nuvem, destruindo o ambiente de homologação da equipe. (Nota: O arquivo .env.local já é ignorado por padrão no .gitignore criado pelo Next.js).

9. Qualidade de Código Contínua

Para impedir que a equipe suba "código sujo" (ex: classes do Tailwind bagunçadas):

  1. Instale o organizador do Tailwind e as ferramentas de Git Hooks:
npm install -D prettier prettier-plugin-tailwindcss husky lint-staged
  1. Crie o arquivo .prettierrc na raiz para ativar a auto-formatação do Tailwind:
{
  "plugins": ["prettier-plugin-tailwindcss"]
}
  1. Inicialize o Husky (ele criará a pasta .husky automaticamente):
npx husky init
  1. Configure o escaneamento inteligente adicionando este bloco na raiz do seu package.json:
"lint-staged": {
  "*.{ts,tsx}": [
    "eslint --fix",
    "prettier --write"
  ]
}
  1. Por fim, abra o arquivo recém-criado .husky/pre-commit e substitua tudo por:
npx lint-staged

Com isso, toda vez que um desenvolvedor digitar git commit, o código será formatado e limpo automaticamente ANTES de ir para a nuvem.


10. A Cultura de Qualidade (Testes Automatizados)

Um projeto corporativo deve nascer com os robôs de teste configurados desde o Dia 1. Instalaremos o Vitest (para testes unitários) e o Playwright (para testes E2E).

1. Instalação do Vitest (Unitário):

npm install -D vitest @vitejs/plugin-react jsdom @testing-library/react

Abra o seu package.json e adicione a linha "test": "vitest" dentro da chave "scripts".

2. Instalação do Playwright (E2E):

npx create-playwright

(Quando o instalador perguntar, escolha Typescript, coloque os testes na pasta tests e confirme a instalação dos navegadores).

3. Automação CI/CD no GitHub Actions: Para que o Playwright atue como "Guardião" e barre códigos quebrados nos seus Pull Requests, crie o arquivo .github/workflows/playwright.yml na raiz do projeto com o código exato abaixo:

name: Playwright E2E Tests
on:
  pull_request:
    branches: [ staging, main ]
jobs:
  test:
    timeout-minutes: 60
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-node@v4
      with:
        node-version: lts/*
    - name: Instalar dependências
      run: npm ci
    - name: Instalar Navegadores do Playwright
      run: npx playwright install --with-deps
    - name: Rodar testes do Playwright
      run: npx playwright test
    - uses: actions/upload-artifact@v4
      if: always()
      with:
        name: playwright-report
        path: playwright-report/
        retention-days: 30

Com isso, a infraestrutura já nasce blindada contra regressões.


11. O Escudo de Segurança (.gitignore)

Antes de darmos o primeiro commit, é crítico garantir que segredos corporativos e caches não vazem para o repositório (GitHub).

O create-next-app já ignora arquivos pesados (node_modules, .next) e variáveis de ambiente tradicionais (.env.local, .env). Porém, nossa arquitetura Híbrida exige proteções extras. Abra o arquivo .gitignore e adicione rigorosamente as seguintes linhas ao final:

# Arquivos Locais da CLI do Supabase
supabase/.temp/

# Arquivos de Configuração da CLI da Vercel
.vercel/

# Chaves de Acesso de Multi-Contas
.envrc

12. Ligar os Motores (Isolamento de Terminais)

Para que você não perca o controle visual dos logs do sistema (principalmente quando as Edge Functions ou o backend derem erro), nós evitamos a abordagem de juntar tudo num único terminal. A arquitetura de elite exige separação de contextos.

A partir de agora, sempre que sentar para trabalhar, abra duas abas de terminal na pasta do projeto:

Terminal 1 (Backend - Supabase):

supabase start

(Ele acordará o Docker, o Banco de Dados, o Auth e o Storage em background).

Terminal 2 (Frontend - Next.js):

npm run dev

Essa separação limpa garante que, se o Next.js der erro de renderização de interface, o log sujo estará apenas no Terminal 2. E quando formos debugar o Backend no futuro, usaremos terminais exclusivos para as funções (assunto que cobriremos a fundo no Manual de Edge Functions).

O Buraco Negro de E-mails (Inbucket): Ao testar a criação de usuários ou redefinição de senhas, o Supabase Local não envia e-mails de verdade para a internet. Ele intercepta e prende todos os e-mails do sistema de autenticação em uma caixa de entrada local. Para ler esses e-mails de teste (e clicar nos links de confirmação), você deve acessar http://localhost:54324 no seu navegador.

(Nota: Ferramentas da nuvem como o Vercel KV serão acessadas automaticamente pelo Next.js usando as chaves baixadas no Passo 6).

A partir daqui, você está livre para focar apenas em construir as lógicas e telas incríveis do seu SaaS!

Como boa prática para escalar gerenciando dezenas de clientes simultâneos, jamais misturamos infraestruturas. Cada cliente possui sua própria conta isolada no Supabase e na Vercel desde o Dia 1.

Para aprender a blindar essa infraestrutura recém-nascida contra bugs antes mesmo de escrever a primeira tela, siga obrigatoriamente para o próximo capítulo: Testes Automatizados: A Cultura Pragmática B2B (Playwright e Vitest).


Apêndice: Aprofundamento Arquitetural

Se em algum momento no futuro a complexidade do seu projeto explodir (ex: você precisar criar um App Mobile em React Native compartilhando código, ou separar a equipe do Marketing da equipe do Dashboard), a fundação que você acabou de criar permite uma migração tranquila.

Para estudos avançados sobre quando e como dar esse próximo passo, leia nossa documentação sobre Monorepos:

  1. Turborepo vs Next.js Route Groups
  2. Turborepo na Vercel: A Engenharia por trás dos Monorepos Corporativos