Build This Now
Build This Now
Was ist der Claude Code?Claude Code installierenClaude Code Native InstallerDein erstes Claude Code-Projekt
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: 1.000 Subagents auf einer echten Codebase orchestrierenClaude Code Best PracticesClaude Opus 4.7 Best PracticesClaude Code auf einem VPSGit-IntegrationClaude Code ReviewClaude Code WorktreesClaude Code Remote ControlClaude Code ChannelsChannels, Routines, Teleport, DispatchGeplante Aufgaben mit Claude CodeClaude Code BerechtigungenClaude Code Auto-ModusStripe-Zahlungen mit Claude Code einbauenFeedback-LoopsTodo-WorkflowsClaude Code TasksProjekt-TemplatesClaude Code Preise und Token-NutzungClaude Code Preise: Was du wirklich zahlstClaude Code Ultra ReviewEine Next.js-App mit Claude Code bauenSupabase DatabaseVercel DeepsecTest-Driven DevelopmentSo baust du ein SaaS-MVP mit Claude CodeAuthentifizierung mit Claude Code einbauen (Supabase Auth)Transaktions-E-Mails mit Claude Code einbauen (Resend + React Email)Eine typsichere API mit Claude Code bauen (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 GuideAgentic Commerce: Wie du eine App baust, für die KI-Agents bezahlen können1M ContextUser API KeysAudit LogsCSV Import PipelineDatabase MigrationsProduction Error TrackingFeature FlagsGitHub ActionsHeadless ModeMax Plan vs APICaching and RevalidationIn-App NotificationsOutbound WebhooksPrompt CachingRoles & PermissionsMarketplace PaymentsUsage-Based BillingWas es 2026 kostet, ein SaaS mit Claude Code zu bauenParallel AI AgentsCoding Agent Injection
speedy_devvkoen_salo
Blog/Handbook/Workflow/SaaS MVP

So baust du ein SaaS-MVP mit Claude Code

Ein Wochenend-Build-Log: ein Next.js-16-Projekt aufsetzen, Supabase Auth und Postgres ergänzen, Stripe-Abrechnung anbinden und auf Vercel deployen, alles mit Claude Code.

Sie möchten das Framework hinter diesen Projekten?

Holen Sie sich das Claude Code System, mit dem wir produktionsreife Software planen, bauen, testen und ausliefern.

Sehen Sie, was wir für Unternehmen bauen →
speedy_devvkoen_salo
speedy_devvWritten by speedy_devvPublished Jul 14, 202614 min readHandbook hubWorkflow index

Letztes Wochenende habe ich eine kleine Feedback-Board-App komplett mit Claude Code gebaut: einloggen, ein Board erstellen, Feature-Wünsche sammeln, hochvoten, $19 im Monat zahlen, um mehr als ein Board freizuschalten. Nichts daran ist kompliziert, und genau das ist der Punkt. Unten findest du den echten Build, in der Reihenfolge, mit dem Code, der es ins Repo geschafft hat.

Ein kleines, echtes SaaS zum Bauen auswählen

Die Beispiel-App heißt Signal. Es ist ein öffentliches Feedback-Board: Ein Gründer erstellt ein Board, teilt den Link, Nutzer reichen Feature-Wünsche ein und voten die hoch, die sie wollen. Kostenlose Accounts bekommen ein Board. Zahlende Accounts bekommen unbegrenzt viele.

Es ist klein genug, um an einem Wochenende fertig zu werden, und vollständig genug, um jede Schicht anzufassen, die ein echtes SaaS braucht: Auth, ein relationales Schema mit Row-Level-Security, ein Kern-Feature-Loop und wiederkehrende Abrechnung. Wenn du etwas anderes baust, gilt die Form der Arbeit unten trotzdem. Tausch Boards und Posts einfach gegen die Kern-Objekte deines Produkts.

Bevor du anfängst

Vier Dinge müssen existieren, bevor du Claude Code öffnest.

Node.js 20.9.0 oder neuer, denn Next.js 16 hat den Support für Node 18 fallen gelassen:

node --version

Claude Code global installiert, plus ein Claude Pro- oder Max-Plan. Die kostenlose Stufe funktioniert nicht mit Claude Code.

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

Accounts bei Supabase, Stripe und Vercel, alle mit großzügigen kostenlosen Stufen für ein Projekt dieser Größe. Leg das Supabase-Projekt und den Stripe-Account jetzt an, du brauchst in den nächsten Schritten API-Keys von beiden.

Ein GitHub-Repo, damit das Vercel-Deploy am Ende ein einziger Push ist statt eines manuellen Uploads.

Das Projekt aufsetzen

Starte von einem sauberen Next.js-16-Projekt, bei dem Turbopack und Tailwind CSS v4 schon eingerichtet sind.

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

Installiere die Pakete, auf denen der Rest dieses Builds aufbaut: den Supabase-SSR-Client für Auth und Postgres, und das Stripe-SDK für die Abrechnung.

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

Öffne das Projekt in Claude Code:

claude

CLAUDE.md und AGENTS.md schreiben

Claude Code liest AGENTS.md für Framework-Konventionen und CLAUDE.md für projektspezifische Regeln. Auf dem Canary-Release von Next.js 16 werden beide für dich generiert. Wenn du auf Stable bist, erstell AGENTS.md selbst mit einer Zeile, die auf die mitgelieferten Docs zeigt:

node_modules/next/dist/docs/

Dann schreib CLAUDE.md. Das ist die Datei, die Claude davon abhält, in jeder Session bei deinem Stack, deinem Datei-Layout und deinen Namenskonventionen zu raten.

@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)

Die Zeile zur Row-Level-Security zählt mehr, als sie aussieht. Ohne sie explizit zu nennen, schreibt Claude manchmal eine Tabelle und vergisst, RLS darauf zu aktivieren, was in Supabase bedeutet, dass standardmäßig jede Zeile für alle lesbar ist.

Das Datenbank-Schema im Plan mode planen

Bevor irgendein Code geschrieben wird, nutze den Plan mode, um das Schema auszuarbeiten.

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."

Claude kommt mit vier Tabellen zurück (profiles, boards, posts, votes), den Foreign Keys dazwischen und einem Plan für RLS: Boards und Posts sind öffentlich lesbar, damit ein Board-Link auch für anonyme Besucher funktioniert, aber Schreibzugriffe verlangen einen authentifizierten Nutzer, dem die Ressource gehört. Sieh dir das an, bevor irgendetwas gebaut wird. Schema-Entscheidungen sind die teuersten, die man zurücknehmen muss, sobald echte Daten in den Tabellen liegen.

Supabase und Postgres einrichten

Erstell ein neues Supabase-Projekt über das Dashboard und lass dann das Schema durch den SQL-Editor laufen. Das ist die tatsächliche Migration, die live gegangen ist:

-- 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;

Jede Tabelle hat RLS aktiviert und eine Policy. Boards und Posts sind für alle lesbar, denn der ganze Sinn eines Feedback-Boards ist ein öffentlicher Link, aber nur der Owner (oder ein authentifizierter Voter, bei den Votes) kann darauf schreiben. Hol dir die Projekt-URL und den Anon-Key aus den API-Einstellungen von Supabase und trag sie in .env.local ein.

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

Auth mit Supabase

Zwei Supabase-Clients decken die ganze App ab: einer, der auf dem Server mit der Session des Besuchers läuft, und einer mit dem Service-Role-Key, der RLS für den späteren Stripe-Webhook umgeht.

// 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 } }
  );
}

proxy.ts (nicht middleware.ts, den Namen gibt es in Next.js 16 nicht mehr) schützt die Dashboard-Routen, indem es nach einer Session prüft, bevor der Request die Seite erreicht.

// 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*"],
};

Die Login-Seite selbst ist ein schlichtes Formular mit E-Mail und Passwort, hinterlegt mit einer Server Action. Nichts Ausgefallenes, Magic Links würden genauso gut funktionieren, wenn du Passwörter lieber ganz überspringen willst.

// 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");
}

Das Kern-Feature bauen: Boards und Upvotes

Das Dashboard listet die Boards eines Nutzers und lässt ihn ein neues erstellen. Kostenlose Accounts sind auf ein Board gedeckelt, erzwungen in der Server Action, nicht nur im 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");
}

Die öffentliche Board-Seite ist der Ort, an dem async Params zählen. In Next.js 16 ist params ein Promise, und await params ist Pflicht, bevor du den Slug lesen kannst.

// 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>
  );
}

Voten braucht eine kleine Client Component, weil es auf einen Klick reagiert. Der Vote selbst läuft über eine Server Action, damit die RLS-Prüfung auf dem Server passiert, nicht im 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 });
  }
}

Das unique (post_id, voter_id)-Constraint aus dem Schema macht hier die eigentliche Arbeit. Wenn ein Nutzer zweimal votet, schlägt der Insert fehl, der Zähler steigt nicht, und es braucht keine zusätzliche Anwendungslogik, um Doppel-Votes zu verhindern.

Stripe Checkout für den Pro-Plan einbauen

Erstell zuerst ein Produkt und einen wiederkehrenden Preis im Stripe-Dashboard, dann verbinde den Checkout-Flow. Den Checkout zu starten ist eine Server Action, die direkt zu Stripe weiterleitet.

// 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!);
}

Der Plan wechselt erst dann wirklich, wenn Stripe das Abo bestätigt, über einen Webhook, nicht beim Success-Redirect. Redirects können gefälscht oder unterbrochen werden, Webhooks sind die Source of Truth.

// 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 });
}

Leite Events während des Testens mit der Stripe CLI an deinen lokalen Server weiter und kopier das Signing-Secret, das sie ausgibt, in STRIPE_WEBHOOK_SECRET.

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

Die Abrechnungsseite selbst ändert sich kaum, also ist sie ein sinnvoller Ort, um zur "use cache"-Direktive zu greifen, die in Next.js 16 experimental.dynamicIO ersetzt hat.

// 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>
  );
}

Quality Gates, bevor du ausliefern

Zwei Checks laufen vor jedem Commit, ohne Ausnahme.

npx tsc --noEmit
npm run build

Lass Claude Code beide laufen, nachdem du den Webhook und den Abrechnungs-Flow verbunden hast, denn Stripes Typen sind streng und im ersten Anlauf leicht ein bisschen falsch zu treffen.

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

Das ist auch der Punkt, an dem du die Flows tatsächlich von Hand testest: registrieren, ein Board erstellen, das Limit des kostenlosen Plans erreichen, über Stripes Testmodus upgraden, bestätigen, dass der Webhook den Plan auf Pro umstellt. Nichts davon fängt ein Type-Checker ab. Das muss durchgeklickt werden.

Auf Vercel deployen

Push das Repo zu GitHub und importier es dann in Vercel. Setz jede Environment-Variable aus .env.local im Vercel-Dashboard vor dem ersten Deploy, inklusive der Stripe-Keys und des Supabase-Service-Role-Keys.

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

Was viele übersehen: Das Stripe-Webhook-Secret aus deiner lokalen stripe listen-Session ist ein anderes als das, das du bekommst, wenn du im Stripe-Dashboard einen echten Endpoint registrierst, der auf deine Produktions-URL zeigt. Erstell diesen Endpoint nach dem ersten Deploy, aktualisiere dann STRIPE_WEBHOOK_SECRET in Vercel mit dem neuen Secret und deploye neu.

Wie eine koordinierte Pipeline aussieht

Eine einzelne Claude-Code-Session, wie die oben, bewältigt ein Wochenendprojekt problemlos. Der Flaschenhals bist du: den Plan durchsehen, die RLS-Policies lesen, den Checkout-Flow von Hand durchklicken. Das bleibt so, egal wie gut der Agent ist, und für vier Features ist das ein vernünftiges Maß an Review.

Es hört auf, vernünftig zu sein, sobald du zwanzig Features statt vier hast. Genau für diese Lücke ist das $29 Code Kit gebaut: ein Gerüst auf Claude Code, das für jedes Feature automatisch plant, baut, bewertet und testet und ein Quality Gate erzwingt (null Type-Errors, null Lint-Errors, ein sauberer Build), bevor irgendetwas ausgeliefert wird. Der npx tsc --noEmit-Schritt oben ist dasselbe Gate, einmal von Hand ausgeführt. In einer Pipeline läuft es nach jedem einzelnen Feature, ohne dass du danach fragst.

Posted by @speedy_devv

Continue in Workflow

  • Agentic Commerce: Wie du eine App baust, für die KI-Agents bezahlen können
    Ein Guide in einfachem Deutsch zu Agentic Commerce im Jahr 2026: Was x402, ACP und das Machine Payments Protocol tun, plus eine Wochenend-Anleitung, um eine bezahlte API auszuliefern, von der KI-Agents kaufen können.
  • Claude Code Best Practices
    Fünf Gewohnheiten trennen Entwickler, die mit Claude Code liefern: PRDs, modulare CLAUDE.md-Regeln, Custom-Slash-Commands, /clear-Resets und eine System-Evolutions-Denkweise.
  • Claude Code Auto-Modus
    Ein zweites Sonnet-Modell prüft jeden Claude Code-Tool-Aufruf, bevor er ausgeführt wird. Was der Auto-Modus blockiert, was er erlaubt, und die Erlaubnisregeln, die er in deine Einstellungen schreibt.
  • Channels, Routines, Teleport, Dispatch
    Die vier Claude-Code-Features, die Anthropic im März und April 2026 ausgeliefert hat und die die CLI in eine ereignisgesteuerte Koordinationsschicht über Handy, Web und Desktop verwandeln.
  • 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.
  • Claude Code Changelog
    Release-Notizen für Claude Code von der v0.2-Beta bis März 2026. Bare-Modus, Channels-Berechtigungsweiterleitung, OAuth-Fixes und alle 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.
  • Prompt-Vorlagen, die Code liefern
    Zehn Prompt-Rezepte, die Code liefern: Full-Stack-Scaffolding, APIs, Schemas, Tests, Refactoring, Debugging, Reviews und CI. Jeweils mit den Fehlermodi, die es zu vermeiden gilt.

Sie möchten das Framework hinter diesen Projekten?

Holen Sie sich das Claude Code System, mit dem wir produktionsreife Software planen, bauen, testen und ausliefern.

Sehen Sie, was wir für Unternehmen bauen →
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.

Authentifizierung mit Claude Code einbauen (Supabase Auth)

Ergänze E-Mail/Passwort-Signup, Google OAuth, Magic Links, geschützte Routen und Session-Handling in einer Next.js-16-App mit Claude Code und Supabase Auth.

On this page

Ein kleines, echtes SaaS zum Bauen auswählen
Bevor du anfängst
Das Projekt aufsetzen
CLAUDE.md und AGENTS.md schreiben
Das Datenbank-Schema im Plan mode planen
Supabase und Postgres einrichten
Auth mit Supabase
Das Kern-Feature bauen: Boards und Upvotes
Stripe Checkout für den Pro-Plan einbauen
Quality Gates, bevor du ausliefern
Auf Vercel deployen
Wie eine koordinierte Pipeline aussieht

Sie möchten das Framework hinter diesen Projekten?

Holen Sie sich das Claude Code System, mit dem wir produktionsreife Software planen, bauen, testen und ausliefern.

Sehen Sie, was wir für Unternehmen bauen →