Build This Now
Build This Now
O que é o Código Claude?Instalar o Claude CodeInstalador Nativo do Claude CodeO Teu Primeiro Projeto com Claude Code
Failed Payment RecoveryLocalization and i18nPDF InvoicesReferral ProgramTwo-Factor AuthNext.js DevTools MCPNext.js Agent SetupSubscription BillingMulti-Tenant SaaSAI Chat FeatureRate LimitingStripe WebhooksSemantic SearchRealtime UpdatesDrip Email Sequencesv2.1.122 Release NotesClaude Code Dynamic Workflows: Como Orquestrar 1.000 Subagentes Num Codebase RealMelhores Práticas do Claude CodeBoas Práticas para o Claude Opus 4.7Claude Code num VPSIntegração GitRevisão de Código com ClaudeWorktrees no Claude CodeControle Remoto do Claude CodeChannels do Claude CodeChannels, Routines, Teleport, DispatchTarefas Agendadas no Claude CodePermissões do Claude CodeModo Auto do Claude CodeAdicionar Pagamentos Stripe Com o Claude CodeFeedback LoopsFluxos de Trabalho com TodosTarefas no Claude CodeTemplates de ProjetoPreços e Consumo de Tokens no Claude CodePreços do Claude Code: O Que Vais Mesmo PagarClaude Code Ultra ReviewConstruir Uma App Next.js Com o Claude CodeSupabase DatabaseVercel DeepsecTest-Driven DevelopmentComo Construir um MVP de SaaS Com o Claude CodeAdicionar Autenticação Com o Claude Code (Supabase Auth)Adicionar Email Transacional Com o Claude Code (Resend + React Email)Construir Uma API Type-Safe Com o Claude Code (oRPC + Zod)File UploadsBackground Jobs (Inngest)Admin DashboardFull-Text SearchClaude Agent SDKKiro Migration GuideClaude Research AgentLeave Grok BuildRoute Subagent ModelsClaude Monorepo SetupCI Repair AgentVisual Regression TestsAgent Cost DashboardCursor Migration GuideComércio Agêntico: Como Construir uma App Que Agentes de IA Podem Pagar1M ContextUser API KeysAudit LogsCSV Import PipelineDatabase MigrationsProduction Error TrackingFeature FlagsGitHub ActionsHeadless ModeMax Plan vs APICaching and RevalidationIn-App NotificationsOutbound WebhooksPrompt CachingRoles & PermissionsMarketplace PaymentsUsage-Based BillingQuanto Custa Construir um SaaS com Claude Code em 2026Parallel AI AgentsCoding Agent Injection
speedy_devvkoen_salo
Blog/Handbook/Workflow/SaaS MVP

Como Construir um MVP de SaaS Com o Claude Code

Um diário de construção de fim de semana: montar uma app Next.js 16, adicionar autenticação e Postgres com Supabase, ligar a faturação Stripe e fazer deploy na Vercel, tudo com o Claude Code.

Quer o framework por trás destes projetos?

Obtenha o sistema Claude Code que usamos para planejar, construir, testar e lançar software em produção.

Veja o que construímos para empresas →
speedy_devvkoen_salo
speedy_devvWritten by speedy_devvPublished Jul 14, 202614 min readHandbook hubWorkflow index

No fim de semana passado construí uma pequena app de quadro de feedback de ponta a ponta com o Claude Code: iniciar sessão, criar um quadro, recolher pedidos de funcionalidades, votar neles, pagar $19 por mês para desbloquear mais do que um quadro. Nada nisto é complicado, e é esse o ponto. Em baixo está a construção real, por ordem, com o código que acabou por entrar no repo.

Escolher um SaaS Pequeno e Real Para Construir

A app de exemplo chama-se Signal. É um quadro de feedback público: um fundador cria um quadro, partilha o link, os utilizadores submetem pedidos de funcionalidades e votam nos que querem. Contas gratuitas ficam com um quadro. Contas pagas ficam com quadros ilimitados.

É pequeno o suficiente para acabar num fim de semana e completo o suficiente para tocar em todas as camadas que um SaaS real precisa: autenticação, um esquema relacional com row-level security, um ciclo de funcionalidade central e faturação recorrente. Se estás a construir outra coisa, o formato do trabalho em baixo continua a aplicar-se. Troca quadros e posts pelo que quer que sejam os objetos centrais do teu produto.

Antes de Começar

Quatro coisas têm de existir antes de abrires o Claude Code.

Node.js 20.9.0 ou mais recente, já que o Next.js 16 deixou cair o suporte ao Node 18:

node --version

Claude Code instalado globalmente, e um plano Claude Pro ou Max. O escalão gratuito não funciona com o Claude Code.

npm install -g @anthropic-ai/claude-code

Contas na Supabase, Stripe e Vercel, todas com escalões gratuitos generosos para um projeto deste tamanho. Cria já o projeto Supabase e a conta Stripe, vais precisar das chaves de API de ambos nos próximos passos.

Um repo no GitHub, para que o deploy na Vercel no fim seja um único push em vez de um upload manual.

Montar o Projeto

Arranca a partir de um projeto Next.js 16 limpo, com Turbopack e Tailwind CSS v4 já ligados.

npx create-next-app@latest signal --typescript --tailwind --app --turbopack
cd signal
npx shadcn@latest init

Instala os pacotes de que o resto desta construção depende: o cliente SSR do Supabase para autenticação e Postgres, e o SDK do Stripe para faturação.

npm install @supabase/ssr @supabase/supabase-js stripe zod

Abre o projeto no Claude Code:

claude

Escrever o CLAUDE.md e o AGENTS.md

O Claude Code lê o AGENTS.md para as convenções da framework e o CLAUDE.md para as regras específicas do projeto. Na versão canary do Next.js 16 ambos são gerados por ti. Se estás no stable, cria o AGENTS.md tu mesmo com uma linha a apontar para os docs incluídos:

node_modules/next/dist/docs/

Depois escreve o CLAUDE.md. Este é o ficheiro que impede o Claude de adivinhar a tua stack, a tua organização de ficheiros e as tuas convenções de nomes em cada sessão.

@AGENTS.md

## Stack

- Next.js 16 with App Router (TypeScript)
- Tailwind CSS v4 with shadcn/ui components
- PostgreSQL via Supabase, with row-level security on every table
- Stripe for subscription billing

## File Conventions

- Server Components by default. "use client" only for interactivity.
- Supabase server client: lib/supabase/server.ts
- Supabase admin client (service role, webhook use only): lib/supabase/admin.ts
- Server Actions live next to the routes that use them, in actions.ts files
- Route handlers for webhooks only, under app/api/

## Commands

- Dev server: npm run dev
- Type check: npx tsc --noEmit
- Build: npm run build

## Proxy

- Auth checks live in proxy.ts, not middleware.ts (Next.js 16)

A linha do row-level security importa mais do que parece. Sem ela dita de forma explícita, o Claude às vezes escreve uma tabela e esquece-se de ativar o RLS nela, o que significa que cada linha fica legível para toda a gente por omissão na Supabase.

Planear o Esquema da Base de Dados no Plan Mode

Antes de qualquer código ser escrito, usa o Plan mode para trabalhar o esquema.

claude --permission-mode plan "design the Postgres schema for Signal: boards owned by a user, posts on a board, and votes on a post. Free accounts get 1 board. Paid accounts get unlimited boards. Include row-level security policies."

O Claude volta com quatro tabelas (profiles, boards, posts, votes), as chaves estrangeiras entre elas e um plano para o RLS: os quadros e os posts são publicamente legíveis para que um link de quadro funcione para visitantes anónimos, mas as escritas exigem um utilizador autenticado que seja dono do recurso. Revê isto antes de seja o que for ser construído. As decisões de esquema são as mais caras de reverter depois de teres dados reais nas tabelas.

Configurar o Supabase e o Postgres

Cria um novo projeto Supabase a partir do dashboard, depois corre o esquema pelo editor de SQL. Esta é a migração que efetivamente foi para produção:

-- profiles: one row per user, tracks plan status
create table profiles (
  id uuid primary key references auth.users(id) on delete cascade,
  plan text not null default 'free',
  stripe_customer_id text,
  created_at timestamptz default now()
);

-- boards: one feedback board per row
create table boards (
  id uuid primary key default gen_random_uuid(),
  owner_id uuid references auth.users(id) on delete cascade not null,
  name text not null,
  slug text unique not null,
  created_at timestamptz default now()
);

-- posts: feature requests on a board
create table posts (
  id uuid primary key default gen_random_uuid(),
  board_id uuid references boards(id) on delete cascade not null,
  title text not null,
  body text,
  vote_count int not null default 0,
  created_at timestamptz default now()
);

-- votes: one vote per user per post
create table votes (
  id uuid primary key default gen_random_uuid(),
  post_id uuid references posts(id) on delete cascade not null,
  voter_id uuid references auth.users(id) on delete cascade not null,
  created_at timestamptz default now(),
  unique (post_id, voter_id)
);

alter table profiles enable row level security;
alter table boards enable row level security;
alter table posts enable row level security;
alter table votes enable row level security;

create policy "Users manage their own profile"
  on profiles for all
  using (auth.uid() = id);

create policy "Boards are publicly readable"
  on boards for select
  using (true);

create policy "Owners manage their own boards"
  on boards for insert, update, delete
  using (auth.uid() = owner_id);

create policy "Posts are publicly readable"
  on posts for select
  using (true);

create policy "Authenticated users create posts"
  on posts for insert
  with check (auth.role() = 'authenticated');

create policy "Voters manage their own votes"
  on votes for all
  using (auth.uid() = voter_id);

-- auto-create a profile row on signup
create or replace function public.handle_new_user()
returns trigger as $$
begin
  insert into public.profiles (id) values (new.id);
  return new;
end;
$$ language plpgsql security definer;

create trigger on_auth_user_created
  after insert on auth.users
  for each row execute function public.handle_new_user();

-- atomic vote increment, called from a Server Action
create or replace function increment_vote(target_post_id uuid)
returns void as $$
begin
  update posts set vote_count = vote_count + 1 where id = target_post_id;
end;
$$ language plpgsql security definer;

Todas as tabelas têm o RLS ativado e uma política. Os quadros e os posts são legíveis por qualquer pessoa, já que o objetivo de um quadro de feedback é um link público, mas só o dono (ou um votante autenticado, no caso dos votos) pode escrever neles. Agarra o URL do projeto e a anon key nas definições de API da Supabase e mete-os no .env.local.

NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key

Autenticação Com o Supabase

Dois clientes Supabase cobrem a app inteira: um que corre no servidor com a sessão do visitante, e outro com a service role key que salta o RLS para o webhook do Stripe mais à frente.

// lib/supabase/server.ts
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: () => cookieStore.getAll(),
        setAll: (cookiesToSet) => {
          cookiesToSet.forEach(({ name, value, options }) =>
            cookieStore.set(name, value, options)
          );
        },
      },
    }
  );
}
// lib/supabase/admin.ts
import { createClient as createSupabaseClient } from "@supabase/supabase-js";

export function createAdminClient() {
  return createSupabaseClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.SUPABASE_SERVICE_ROLE_KEY!,
    { auth: { persistSession: false } }
  );
}

O proxy.ts (não middleware.ts, esse nome desapareceu no Next.js 16) protege as rotas do dashboard ao verificar se existe uma sessão antes de o pedido chegar à página.

// proxy.ts
import { NextResponse, type NextRequest } from "next/server";
import { createServerClient } from "@supabase/ssr";

export async function proxy(request: NextRequest) {
  const response = NextResponse.next({ request });

  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll: () => request.cookies.getAll(),
        setAll: (cookiesToSet) => {
          cookiesToSet.forEach(({ name, value, options }) =>
            response.cookies.set(name, value, options)
          );
        },
      },
    }
  );

  const {
    data: { user },
  } = await supabase.auth.getUser();

  if (!user && request.nextUrl.pathname.startsWith("/dashboard")) {
    return NextResponse.redirect(new URL("/login", request.url));
  }

  return response;
}

export const config = {
  matcher: ["/dashboard/:path*"],
};

A página de login é apenas um formulário de email e password suportado por uma Server Action. Nada de especial, magic links funcionariam igualmente bem se preferires saltar as passwords por completo.

// app/login/actions.ts
"use server";

import { redirect } from "next/navigation";
import { createClient } from "@/lib/supabase/server";

export async function signIn(formData: FormData) {
  const supabase = await createClient();

  const { error } = await supabase.auth.signInWithPassword({
    email: formData.get("email") as string,
    password: formData.get("password") as string,
  });

  if (error) redirect("/login?error=invalid-credentials");
  redirect("/dashboard");
}

export async function signUp(formData: FormData) {
  const supabase = await createClient();

  const { error } = await supabase.auth.signUp({
    email: formData.get("email") as string,
    password: formData.get("password") as string,
  });

  if (error) redirect("/login?error=signup-failed");
  redirect("/dashboard");
}

Construir a Funcionalidade Central: Quadros e Votos

O dashboard lista os quadros de um utilizador e deixa-o criar um novo. Contas gratuitas ficam limitadas a um quadro, imposto na Server Action, não só na UI.

// app/dashboard/actions.ts
"use server";

import { createClient } from "@/lib/supabase/server";
import { redirect } from "next/navigation";

export async function createBoard(formData: FormData) {
  const supabase = await createClient();
  const {
    data: { user },
  } = await supabase.auth.getUser();
  if (!user) redirect("/login");

  const { data: profile } = await supabase
    .from("profiles")
    .select("plan")
    .eq("id", user.id)
    .single();

  const { count } = await supabase
    .from("boards")
    .select("id", { count: "exact", head: true })
    .eq("owner_id", user.id);

  if (profile?.plan === "free" && (count ?? 0) >= 1) {
    redirect("/dashboard/billing?limit=reached");
  }

  const name = formData.get("name") as string;
  const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 40);

  await supabase.from("boards").insert({ owner_id: user.id, name, slug });
  redirect("/dashboard");
}

A página pública do quadro é onde os params assíncronos importam. No Next.js 16, params é uma Promise, e await params é obrigatório antes de conseguires ler o slug.

// app/b/[slug]/page.tsx
import { createClient } from "@/lib/supabase/server";
import { VoteButton } from "./vote-button";
import { notFound } from "next/navigation";

interface PageProps {
  params: Promise<{ slug: string }>;
}

export default async function BoardPage({ params }: PageProps) {
  const { slug } = await params;
  const supabase = await createClient();

  const { data: board } = await supabase
    .from("boards")
    .select("id, name")
    .eq("slug", slug)
    .single();

  if (!board) notFound();

  const { data: posts } = await supabase
    .from("posts")
    .select("id, title, body, vote_count")
    .eq("board_id", board.id)
    .order("vote_count", { ascending: false });

  return (
    <main className="max-w-2xl mx-auto py-12 px-4">
      <h1 className="text-2xl font-bold mb-6">{board.name}</h1>
      <ul className="space-y-3">
        {posts?.map((post) => (
          <li key={post.id} className="flex gap-4 border rounded-lg p-4">
            <VoteButton postId={post.id} initialCount={post.vote_count} />
            <div>
              <p className="font-medium">{post.title}</p>
              {post.body && (
                <p className="text-sm text-muted-foreground">{post.body}</p>
              )}
            </div>
          </li>
        ))}
      </ul>
    </main>
  );
}

Votar precisa de um pequeno Client Component, já que responde a um clique. O voto em si corre por uma Server Action para que a verificação do RLS aconteça no servidor, não no browser.

// app/b/[slug]/vote-button.tsx
"use client";

import { useState, useTransition } from "react";
import { castVote } from "./actions";

export function VoteButton({
  postId,
  initialCount,
}: {
  postId: string;
  initialCount: number;
}) {
  const [count, setCount] = useState(initialCount);
  const [isPending, startTransition] = useTransition();

  return (
    <button
      disabled={isPending}
      onClick={() =>
        startTransition(async () => {
          setCount((c) => c + 1);
          await castVote(postId);
        })
      }
      className="flex flex-col items-center justify-center w-12 h-12 rounded-md border hover:bg-accent"
    >
      <span className="text-sm font-semibold">{count}</span>
    </button>
  );
}
// app/b/[slug]/actions.ts
"use server";

import { createClient } from "@/lib/supabase/server";
import { redirect } from "next/navigation";

export async function castVote(postId: string) {
  const supabase = await createClient();
  const {
    data: { user },
  } = await supabase.auth.getUser();
  if (!user) redirect("/login");

  const { error } = await supabase
    .from("votes")
    .insert({ post_id: postId, voter_id: user.id });

  if (!error) {
    await supabase.rpc("increment_vote", { target_post_id: postId });
  }
}

A constraint unique (post_id, voter_id) do esquema é que faz o trabalho de verdade aqui. Se um utilizador votar duas vezes, o insert falha, a contagem não incrementa, e não é preciso lógica de aplicação extra para impedir o voto duplo.

Adicionar o Stripe Checkout Para o Plano Pro

Cria primeiro um produto e um preço recorrente no dashboard do Stripe, depois liga o fluxo de checkout. Iniciar o checkout é uma Server Action que redireciona diretamente para o Stripe.

// app/dashboard/billing/actions.ts
"use server";

import Stripe from "stripe";
import { redirect } from "next/navigation";
import { createClient } from "@/lib/supabase/server";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export async function startCheckout() {
  const supabase = await createClient();
  const {
    data: { user },
  } = await supabase.auth.getUser();
  if (!user) redirect("/login");

  const session = await stripe.checkout.sessions.create({
    mode: "subscription",
    line_items: [{ price: process.env.STRIPE_PRO_PRICE_ID!, quantity: 1 }],
    success_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard?upgraded=true`,
    cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard/billing`,
    client_reference_id: user.id,
    metadata: { supabase_user_id: user.id },
  });

  redirect(session.url!);
}

O plano só muda de verdade depois de o Stripe confirmar a subscrição, através de um webhook, não no redirect de sucesso. Os redirects podem ser falsificados ou interrompidos, os webhooks é que são a fonte da verdade.

// app/api/webhooks/stripe/route.ts
import Stripe from "stripe";
import { NextRequest, NextResponse } from "next/server";
import { createAdminClient } from "@/lib/supabase/admin";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;

export async function POST(req: NextRequest) {
  const body = await req.text();
  const signature = req.headers.get("stripe-signature");

  if (!signature) {
    return NextResponse.json({ error: "Missing signature" }, { status: 400 });
  }

  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(body, signature, webhookSecret);
  } catch {
    return NextResponse.json({ error: "Invalid signature" }, { status: 400 });
  }

  const supabase = createAdminClient();

  if (event.type === "checkout.session.completed") {
    const session = event.data.object as Stripe.Checkout.Session;
    const userId = session.metadata?.supabase_user_id;

    if (userId) {
      await supabase
        .from("profiles")
        .update({
          plan: "pro",
          stripe_customer_id: session.customer as string,
        })
        .eq("id", userId);
    }
  }

  if (event.type === "customer.subscription.deleted") {
    const subscription = event.data.object as Stripe.Subscription;

    await supabase
      .from("profiles")
      .update({ plan: "free" })
      .eq("stripe_customer_id", subscription.customer as string);
  }

  return NextResponse.json({ received: true });
}

Reencaminha os eventos para o teu servidor local enquanto testas com a CLI do Stripe, e copia o signing secret que ela imprime para o STRIPE_WEBHOOK_SECRET.

stripe listen --forward-to localhost:3000/api/webhooks/stripe

A própria página de faturação quase não muda, por isso é um sítio razoável para recorrer à diretiva "use cache" que substituiu o experimental.dynamicIO no Next.js 16.

// app/pricing/page.tsx
"use cache";

export default function PricingPage() {
  return (
    <main className="max-w-2xl mx-auto py-16 px-4">
      <h1 className="text-3xl font-bold mb-8">Pricing</h1>
      <div className="grid grid-cols-2 gap-6">
        <div className="border rounded-lg p-6">
          <h2 className="font-semibold">Free</h2>
          <p className="text-sm text-muted-foreground">1 board, unlimited posts</p>
        </div>
        <div className="border rounded-lg p-6">
          <h2 className="font-semibold">Pro ($19/mo)</h2>
          <p className="text-sm text-muted-foreground">Unlimited boards</p>
        </div>
      </div>
    </main>
  );
}

Barreiras de Qualidade Antes de Pores em Produção

Duas verificações correm antes de cada commit, sem exceções.

npx tsc --noEmit
npm run build

Pede ao Claude Code para correr as duas depois de ligares o webhook e o fluxo de faturação, já que os tipos do Stripe são estritos e fáceis de acertar só por pouco à primeira.

claude "run tsc --noEmit and fix any type errors, then confirm the build passes"

Este é também o sítio onde efetivamente testas os fluxos à mão: regista-te, cria um quadro, atinge o limite do plano gratuito, faz upgrade pelo modo de teste do Stripe, confirma que o webhook vira o plano para pro. Nada disto é apanhado por um verificador de tipos. Tem de ser clicado à mão.

Fazer Deploy na Vercel

Faz push do repo para o GitHub, depois importa-o na Vercel. Define todas as variáveis de ambiente do .env.local no dashboard da Vercel antes do primeiro deploy, incluindo as chaves do Stripe e a service role key do Supabase.

npx vercel env add SUPABASE_SERVICE_ROLE_KEY production
npx vercel env add STRIPE_SECRET_KEY production
npx vercel env add STRIPE_WEBHOOK_SECRET production
npx vercel --prod

Uma coisa que as pessoas deixam passar: o webhook secret da tua sessão local de stripe listen é diferente do que recebes quando registas um endpoint real no dashboard do Stripe a apontar para o teu URL de produção. Cria esse endpoint depois do primeiro deploy, depois atualiza o STRIPE_WEBHOOK_SECRET na Vercel com o novo secret e volta a fazer deploy.

Como É Um Pipeline Coordenado

Uma única sessão do Claude Code, como a de cima, aguenta bem um projeto de fim de semana. O estrangulamento és tu: rever o plano, ler as políticas de RLS, clicar pelo fluxo de checkout à mão. Isso continua a ser verdade por muito bom que o agente seja, e é uma quantidade razoável de revisão para quatro funcionalidades.

Deixa de ser razoável assim que tens vinte funcionalidades em vez de quatro. É essa a lacuna para que o Code Kit de $29 foi feito: uma estrutura por cima do Claude Code que corre plano, construção, avaliação e teste em cada funcionalidade automaticamente, e impõe uma barreira de qualidade (zero erros de tipos, zero erros de lint, uma build limpa) antes de seja o que for ir para produção. O passo npx tsc --noEmit de cima é a mesma barreira, corrida à mão uma vez. Num pipeline, corre depois de cada funcionalidade sem tu pedires.

Posted by @speedy_devv

Continue in Workflow

  • Comércio Agêntico: Como Construir uma App Que Agentes de IA Podem Pagar
    Um guia em português simples sobre comércio agêntico em 2026: o que fazem o x402, o ACP e o Machine Payments Protocol, mais um passo a passo de fim de semana para lançar uma API paga que agentes de IA podem comprar.
  • Melhores Práticas do Claude Code
    Cinco hábitos separam os engenheiros que entregam com Claude Code: PRDs, regras modulares em CLAUDE.md, slash commands personalizados, resets com /clear e uma mentalidade de evolução do sistema.
  • Modo Auto do Claude Code
    Um segundo modelo Sonnet revê cada chamada de ferramenta do Claude Code antes de ser executada. O que o modo auto bloqueia, o que permite e as regras de permissão que cria nas tuas definições.
  • Channels, Routines, Teleport, Dispatch
    As quatro funcionalidades de Claude Code que a Anthropic lançou em março e abril de 2026 e que transformam a CLI numa camada de coordenação orientada a eventos entre telemóvel, web e desktop.
  • Claude Code 1M Context in Practice: When Bigger Isn't Better
    The 1M-token context window is GA at flat pricing, but bigger isn't always better. A decision framework, token-cost math, and when to use /compact, subagents, and dynamic workflows instead.
  • How to Build an Admin Dashboard With Claude Code
    Ship an internal admin panel with Claude Code: role-gated routes, a searchable users and orders table with pagination, impersonation-safe RLS, and metrics tiles pulled through a type-safe API.

More from Handbook

  • Best SaaS Boilerplate 2026: The Honest Comparison
    An honest 2026 roundup of the best SaaS boilerplates and starter kits (ShipFast, Makerkit, Supastarter, SaaS Pegasus, Divjoy, open source), with real pricing, stacks, and the trade-off nobody mentions: a boilerplate still leaves you coding.
  • Changelog do Claude Code
    Notas de cada versão do Claude Code desde a beta v0.2 até março de 2026. Bare mode, relay de permissões com Channels, correções de OAuth e todas as breaking changes.
  • What It Really Costs to Build a SaaS MVP in 2026
    A buyer's cost breakdown for building a SaaS MVP in 2026. Real freelancer, agency, no-code, AI-tool, and DIY numbers with citations, plus where the money actually goes.
  • Templates de Prompts que Entregam Código
    Dez receitas de prompts que entregam código: scaffolding full-stack, APIs, schemas, testes, refatorações, debugging, reviews e CI. Cada uma com os erros a evitar.

Quer o framework por trás destes projetos?

Obtenha o sistema Claude Code que usamos para planejar, construir, testar e lançar software em produção.

Veja o que construímos para empresas →
speedy_devvkoen_salo

Test-Driven Development

Make Claude write failing tests from your spec, then implement until green without cheating. How to wire testing into the agent loop so quality is enforced, not hoped for.

Adicionar Autenticação Com o Claude Code (Supabase Auth)

Adiciona registo por email/password, Google OAuth, magic links, rotas protegidas e gestão de sessões a uma app Next.js 16 usando o Claude Code e o Supabase Auth.

On this page

Escolher um SaaS Pequeno e Real Para Construir
Antes de Começar
Montar o Projeto
Escrever o CLAUDE.md e o AGENTS.md
Planear o Esquema da Base de Dados no Plan Mode
Configurar o Supabase e o Postgres
Autenticação Com o Supabase
Construir a Funcionalidade Central: Quadros e Votos
Adicionar o Stripe Checkout Para o Plano Pro
Barreiras de Qualidade Antes de Pores em Produção
Fazer Deploy na Vercel
Como É Um Pipeline Coordenado

Quer o framework por trás destes projetos?

Obtenha o sistema Claude Code que usamos para planejar, construir, testar e lançar software em produção.

Veja o que construímos para empresas →