Build This Now
Build This Now
クロード・コードとは何か?Claude Code のインストールClaude Code ネイティブインストーラー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:実際のコードベースで 1,000 個の subagents を動かす方法Claude Code ベストプラクティスClaude Opus 4.7 ベストプラクティスVPS上でのClaude CodeGit 統合Claude Code レビューClaude Code WorktreesClaude CodeリモートコントロールClaude Code ChannelsChannels、Routines、Teleport、DispatchClaude Code スケジュールタスクClaude Code権限管理Claude Code オートモードClaude Code で Stripe 決済を組み込むフィードバックループTodoワークフローClaude Code タスク管理プロジェクトテンプレートClaude Code の料金とトークン使用量Claude Code の料金:実際にいくら払うことになるのかClaude Code Ultra Review 完全ガイドClaude Code で Next.js アプリを作るSupabase DatabaseVercel DeepsecTest-Driven DevelopmentClaude Code で SaaS の MVP を作る方法Claude Code で認証を追加する(Supabase Auth)Claude Code でトランザクションメールを追加する(Resend + React Email)Claude Code で型安全な API を作る(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 Guideエージェント型コマース:AI エージェントが支払えるアプリの作り方1M ContextUser API KeysAudit LogsCSV Import PipelineDatabase MigrationsProduction Error TrackingFeature FlagsGitHub ActionsHeadless ModeMax Plan vs APICaching and RevalidationIn-App NotificationsOutbound WebhooksPrompt CachingRoles & PermissionsMarketplace PaymentsUsage-Based Billing2026年、Claude Code で SaaS を作るといくらかかるかParallel AI AgentsCoding Agent Injection
speedy_devvkoen_salo
Blog/Handbook/Workflow/SaaS MVP

Claude Code で SaaS の MVP を作る方法

週末のビルドログ。Next.js 16 アプリの土台を作り、Supabase の認証と Postgres を追加し、Stripe の課金を繋ぎ込み、Vercel にデプロイする。すべて Claude Code で。

設定をやめて、構築を始めよう。

AIオーケストレーション付きSaaSビルダーテンプレート。

企業向けに構築している実績を見る →
speedy_devvkoen_salo
speedy_devvWritten by speedy_devvPublished Jul 14, 202614 min readHandbook hubWorkflow index

先週末、Claude Code で小さなフィードバックボードのアプリを端から端まで作りました。ログインして、ボードを作り、機能リクエストを集め、投票し、月 $19 払えば 2 つ以上のボードを持てる。複雑なところは何もありません。そこが肝心なんです。以下は実際のビルドを順を追って、リポジトリに入った本物のコードとともに紹介します。

作る対象は、小さくて本物の SaaS を選ぶ

サンプルアプリの名前は Signal。公開型のフィードバックボードです。創業者がボードを作ってリンクを共有し、ユーザーが機能リクエストを投稿して、欲しいものに投票します。無料アカウントはボード 1 つ。有料アカウントはボード無制限。

週末で仕上がるくらい小さく、それでいて本物の SaaS に必要なすべての層に触れられるくらい完成度があります。認証、row-level security 付きのリレーショナルスキーマ、コア機能のループ、そして継続課金です。別のものを作るなら、以下の作業の形はそのまま当てはまります。ボードや投稿を、あなたのプロダクトのコアとなるオブジェクトに置き換えてください。

始める前に

Claude Code を開く前に、4 つそろえておく必要があります。

Node.js 20.9.0 以降。 Next.js 16 は Node 18 のサポートを打ち切ったからです:

node --version

Claude Code をグローバルにインストールし、Claude Pro または Max プランに入っていること。無料ティアでは Claude Code は動きません。

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

Supabase・Stripe・Vercel のアカウント。 このくらいの規模のプロジェクトなら、どれも十分な無料枠があります。Supabase のプロジェクトと Stripe のアカウントは今のうちに作っておきましょう。次のステップで両方の API キーが必要になります。

GitHub のリポジトリ。 これがあると、最後の Vercel デプロイが手動アップロードではなく 1 回の push で済みます。

プロジェクトの土台を作る

Turbopack と Tailwind CSS v4 が最初から繋がった、まっさらな Next.js 16 プロジェクトから始めます。

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

このビルドの残りが依存するパッケージを入れます。認証と Postgres 用の Supabase SSR クライアントと、課金用の Stripe SDK です。

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

Claude Code でプロジェクトを開きます:

claude

CLAUDE.md と AGENTS.md を書く

Claude Code はフレームワークの慣習を AGENTS.md から、プロジェクト固有のルールを CLAUDE.md から読みます。Next.js 16 の canary リリースなら両方とも自動生成されます。stable を使っているなら、同梱ドキュメントを指す 1 行だけの AGENTS.md を自分で作りましょう:

node_modules/next/dist/docs/

次に CLAUDE.md を書きます。これが、毎セッションごとにスタック・ファイル構成・命名規則を Claude に推測させないためのファイルです。

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

row-level security の 1 行は、見た目以上に大事です。これを明記しておかないと、Claude はときどきテーブルを作って RLS を有効にし忘れます。すると Supabase では、そのテーブルの全行がデフォルトで誰からでも読める状態になってしまいます。

Plan mode でデータベーススキーマを設計する

コードを 1 行も書く前に、Plan mode でスキーマを詰めておきます。

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 は 4 つのテーブル(profiles・boards・posts・votes)、それらの間の外部キー、そして RLS のプランを返してきます。ボードと投稿は公開読み取り可(ボードのリンクが匿名の訪問者に対しても機能するように)ですが、書き込みには、そのリソースを所有する認証済みユーザーが必要です。何かが作られる前に、これをレビューしましょう。スキーマの判断は、テーブルに本物のデータが入ったあとで巻き戻すのが最もコストの高い判断です。

Supabase と Postgres をセットアップする

ダッシュボードから新しい Supabase プロジェクトを作り、SQL エディタでスキーマを流します。これが実際に出荷されたマイグレーションです:

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

すべてのテーブルで RLS が有効になっていて、ポリシーが付いています。ボードと投稿は誰でも読めます。フィードバックボードの目的そのものが公開リンクだからです。ただし書き込めるのは所有者(投票については認証済みの投票者)だけです。Supabase の API 設定からプロジェクト URL と anon キーを取ってきて、.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

Supabase で認証を作る

アプリ全体は Supabase クライアント 2 つでカバーできます。ひとつは訪問者のセッションでサーバー上を動くもの、もうひとつは後で Stripe の webhook のために RLS をバイパスする service role キーのものです。

// 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(middleware.ts ではありません。その名前は Next.js 16 で消えました)は、リクエストがページに届く前にセッションを確認して、ダッシュボードのルートを保護します。

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

ログインページそのものは、Server Action に紐づいたただのメールとパスワードのフォームです。凝ったところはありません。パスワードを完全に省きたいなら、magic link でも同じように機能します。

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

コア機能を作る:ボードとアップボート

ダッシュボードはユーザーのボードを一覧表示し、新しいボードを作れるようにします。無料アカウントはボード 1 つに制限されますが、これは UI だけでなく Server Action の中で強制します。

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

公開ボードページは、async な params が効いてくる場所です。Next.js 16 では params は Promise なので、slug を読む前に await params が必要です。

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

投票はクリックに反応するので、小さな Client Component が必要です。投票そのものは Server Action を通して走らせるので、RLS のチェックはブラウザではなくサーバー上で行われます。

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

ここで本当の仕事をしているのは、スキーマの unique (post_id, voter_id) 制約です。ユーザーが 2 回投票すると insert が失敗し、カウントは増えず、二重投票を防ぐための追加のアプリケーションロジックは要りません。

Pro プラン向けに Stripe Checkout を追加する

まず Stripe のダッシュボードで商品と継続課金の価格を作ってから、checkout フローを繋ぎます。checkout を始めるのは、Stripe へ直接リダイレクトする Server Action です。

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

プランが実際に変わるのは、成功時のリダイレクトではなく、Stripe が webhook を通じてサブスクリプションを確認したときだけです。リダイレクトは偽装されたり途中で切れたりし得ます。webhook が信頼できる情報源です。

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

テスト中は Stripe CLI でイベントをローカルサーバーに転送し、表示される署名シークレットを STRIPE_WEBHOOK_SECRET にコピーしましょう。

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

課金ページ自体はほとんど変わらないので、Next.js 16 で experimental.dynamicIO に取って代わった "use cache" ディレクティブを試すのにちょうどいい場所です。

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

出荷前の品質ゲート

すべてのコミットの前に、例外なく 2 つのチェックを走らせます。

npx tsc --noEmit
npm run build

webhook と課金フローを繋いだあとは、この両方を Claude Code に実行させましょう。Stripe の型は厳格で、最初のパスでは少しずれやすいからです。

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

ここは、フローを実際に手で確認する場所でもあります。サインアップし、ボードを作り、無料プランの上限に当たり、Stripe のテストモードでアップグレードし、webhook がプランを pro に切り替えることを確認する。どれも型チェッカーでは捕まえられません。実際にクリックして通す必要があります。

Vercel にデプロイする

リポジトリを GitHub に push してから、Vercel にインポートします。最初のデプロイの前に、.env.local のすべての環境変数を Vercel のダッシュボードに設定しましょう。Stripe のキーと Supabase の service role キーも含めてです。

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

見落とされがちな点がひとつ。ローカルの stripe listen セッションで得た Stripe の webhook シークレットは、本番 URL を指す本物のエンドポイントを Stripe ダッシュボードで登録したときに得られるものとは別物です。最初のデプロイのあとにそのエンドポイントを作り、Vercel の STRIPE_WEBHOOK_SECRET を新しいシークレットに更新して、再デプロイしましょう。

連携されたパイプラインはどう見えるか

上のような Claude Code の 1 セッションは、週末プロジェクトなら問題なくこなせます。ボトルネックはあなたです。プランをレビューし、RLS ポリシーを読み、checkout フローを手でクリックして通す。これはエージェントがどれだけ優秀でも変わりませんし、機能 4 つ分としては妥当なレビュー量です。

これが妥当でなくなるのは、機能が 4 つではなく 20 になったときです。そのギャップのために作られたのが $29 の Code Kit です。Claude Code の上に載せる harness で、機能ごとに plan・build・evaluate・test を自動で回し、品質ゲート(型エラーゼロ、lint エラーゼロ、クリーンなビルド)を出荷前に強制します。上の npx tsc --noEmit のステップは、それと同じゲートを手で 1 回走らせているものです。パイプラインでは、あなたが頼まなくても、機能ひとつごとに毎回走ります。

Posted by @speedy_devv

Continue in Workflow

  • エージェント型コマース:AI エージェントが支払えるアプリの作り方
    2026年のエージェント型コマースをわかりやすく解説するガイド。x402、ACP、Machine Payments Protocol が何をするのか、そして AI エージェントが購入できる有料 API を週末で出荷するための手順を紹介します。
  • Claude Code ベストプラクティス
    Claude Codeで成果を出すエンジニアを分ける5つの習慣: PRD、モジュラーなCLAUDE.mdのルール、カスタムスラッシュコマンド、/clearリセット、そしてシステム進化の思考法。
  • Claude Code オートモード
    2つ目の Sonnet モデルが、Claude Code のすべてのツール呼び出しを実行前に審査します。オートモードがブロックするもの・許可するもの、そして settings.json に追加される許可ルールについて解説します。
  • Channels、Routines、Teleport、Dispatch
    Anthropic が2026年3月と4月に出荷した4つの Claude Code 機能。これらは CLI を、スマホ・ウェブ・デスクトップをまたぐイベント駆動の調整レイヤーに変えます。
  • 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 変更履歴
    v0.2ベータから2026年3月までのClaude Codeのリリースノート。ベアモード、チャンネル権限リレー、OAuthの修正、すべての破壊的変更を網羅。
  • 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.
  • コードを生み出すプロンプトテンプレート
    コードを出荷する10のプロンプトレシピ: フルスタックスキャフォールディング、API、スキーマ、テスト、リファクタリング、デバッグ、レビュー、CI。それぞれの失敗パターンも紹介。

設定をやめて、構築を始めよう。

AIオーケストレーション付きSaaSビルダーテンプレート。

企業向けに構築している実績を見る →
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.

Claude Code で認証を追加する(Supabase Auth)

Claude Code と Supabase Auth を使って、Next.js 16 アプリにメール/パスワードのサインアップ、Google OAuth、magic link、保護されたルート、セッション管理を追加する方法。

On this page

作る対象は、小さくて本物の SaaS を選ぶ
始める前に
プロジェクトの土台を作る
CLAUDE.md と AGENTS.md を書く
Plan mode でデータベーススキーマを設計する
Supabase と Postgres をセットアップする
Supabase で認証を作る
コア機能を作る:ボードとアップボート
Pro プラン向けに Stripe Checkout を追加する
出荷前の品質ゲート
Vercel にデプロイする
連携されたパイプラインはどう見えるか

設定をやめて、構築を始めよう。

AIオーケストレーション付きSaaSビルダーテンプレート。

企業向けに構築している実績を見る →