なぜ90日で区切るのか
Stripe のイベントログが 400 で赤く並ぶとき、原因が難所にあることは意外と少ないものです。ヘッダー名を一箇所取り違えているだけで、署名検証を通らなかった Webhook は一件も処理されず、課金状態はデータベースに一度も届きません。画面はきれいに動いているのに、売上だけが記録されない状態になります。
個人開発でSaaSを立ち上げるとき、進行を止めるのはたいていこういう箇所です。難しい設計判断ではなく、確認していなかった一行です。
初収益まで1年以上かかってしまう原因も、手が遅いことより、方向性が定まらないことと、完璧主義でリリースを先送りすることのほうが大きいと感じています。AIエージェントを実装に組み込むと、この構造がはっきりします。手を動かす時間が短くなるぶん、「何を作らないか」を決めていない期間が丸ごと表に出てくるからです。速く作れるということは、間違ったものを速く作れるということでもあります。
90日という区切りは、その判断を先送りできない長さとして選んでいます。以下は、検証・基盤・実装・課金・リリースを、それぞれ引き返せる粒度に割ったものです。各フェーズの終わりには「ここで止めてもよい」判断点を置いています。
Phase 1: アイデア検証フェーズ(Day 1〜14)
Day 1〜3: 問題の言語化とターゲット設定
最初の3日間は、コードを1行も書いてはいけません。この段階での最重要タスクは**「解くべき問題を正確に定義すること」**です。
Antigravityのチャット機能をリサーチパートナーとして活用しましょう。以下のプロンプトが有効です。
あなたは経験豊富なプロダクトマネージャーです。
私は[業界/職種]の人々が抱える[問題]を解決するSaaSを作ろうとしています。
以下の質問に答えてください:
1. この問題が本当に存在するかを検証する方法を5つ挙げてください
2. 競合サービスとその弱点を3つ分析してください
3. このSaaSの初期ターゲット(最初の10人の顧客像)を具体的に描いてください
Antigravityが提示するリサーチフレームワークをもとに、Reddit、Twitter/X、Discord、Slackコミュニティでの声を収集します。この段階で「お金を払ってでも解決したい」と言う人が10人見つかれば、十分なシグナルです。
Day 4〜7: 競合分析とユニークポジションの確立
Antigravityを使って競合サービスの機能一覧を整理します。
以下の競合サービス[A, B, C]の機能を比較分析し、
私のターゲットユーザーにとって最も不満を感じている部分と、
私が勝てる可能性のあるポジションを特定してください。
ターゲット: [具体的なターゲット像]
重要なのは「全機能で勝る」ことではなく、特定の課題において明確に優れること です。Notion対抗のSaaSを作っても勝てませんが、「建設会社の現場監督が使う工事日報管理ツール」なら充分戦えます。
Day 8〜14: MVPスコープの確定とプロトタイプ
この段階でAntigravityが真価を発揮します。Figmaのデザインがなくても、テキストベースの仕様からAntigravityが動くプロトタイプを生成できます。
以下の要件でNext.jsのプロトタイプを作成してください:
- ユーザーが[タスク]を登録できる画面
- 登録したタスクの一覧が表示される画面
- shadcn/uiを使用すること
- データはlocalStorageに保存すること(バックエンドは後で実装)
このプロトタイプをターゲットユーザー5〜10人に見せ、フィードバックを収集します。「使いたいですか?」ではなく「いくらなら払いますか?」と聞くのが重要です。
Phase 2: 技術基盤構築フェーズ(Day 15〜30)
技術スタック選定の考え方
MVPの技術スタックは「最高の技術」ではなく「最速でデプロイできる技術」を選ぶべきです。以下が2026年時点でのAntigravity相性最良スタックです。
フロントエンド + バックエンド(フルスタック)
Next.js 15(App Router)+ TypeScript
shadcn/ui + Tailwind CSS v4
Cloudflare Pages + Workers(エッジデプロイ)
データベース + 認証
Supabase(PostgreSQL + Auth + Realtime + Storage)または Neon
Clerk(認証、特にSaaS向け機能が充実)
決済
Stripe(月額サブスクリプション + ワンタイム対応)
このスタックをAntigravityのプロジェクトコンテキストに設定しておくと、すべての実装提案がこの技術前提で行われます。
Day 15〜17: プロジェクト初期設定
# Antigravityのターミナルで実行
npx create-next-app@latest my-saas --typescript --tailwind --app
cd my-saas
npx shadcn@latest init
プロジェクト作成後、Antigravityに以下のAGENTS.mdの作成を依頼します。
# AGENTS.md
## プロジェクト概要
[ SaaSの名前 ]は[ ターゲット ]向けの[ 問題解決 ]サービスです。
## 技術スタック
- Next.js 15 App Router + TypeScript
- Supabase(DB + Auth)
- Stripe(課金)
- Cloudflare Pages(デプロイ)
- shadcn/ui + Tailwind CSS
## コーディング規約
- コンポーネントはsrc/components/に配置
- APIルートはsrc/app/api/に配置
- 型定義はsrc/types/に集約
- Server Actionsを優先してAPIルートを最小限に
## 重要なビジネスロジック
- サブスクリプションプラン: Free(3プロジェクト)/ Pro(¥980/月・無制限)
- ユーザー認証はClerk経由
このAGENTS.mdがあることで、Antigravityは常にプロジェクトの文脈を理解した提案を行います。
Day 18〜22: 認証基盤の実装
Supabaseの認証をAntigravityで実装する場合、以下のプロンプトが効果的です。
Supabase Auth + Next.js 15 App RouterでGoogle/GitHubソーシャルログインを
実装してください。要件:
- ミドルウェアでルート保護
- ダッシュボードは認証必須
- ランディングページは認証不要
- ログイン/ログアウト後のリダイレクト処理
- TypeScript strict モード対応
Antigravityが生成するコードは、SupabaseのSSR向けクライアント設定から、ミドルウェアの実装まで含む完全なものになります。
Day 23〜30: データモデル設計とCRUD実装
-- Antigravityに依頼するデータモデル設計のプロンプト例
-- 「以下のユーザーストーリーに基づいて、
-- Supabase(PostgreSQL)のテーブル設計をSQLで作成してください」
create table public .projects (
id uuid default gen_random_uuid() primary key ,
user_id uuid references auth . users not null ,
name text not null ,
description text ,
status text default 'active' ,
created_at timestamptz default now (),
updated_at timestamptz default now ()
);
-- Row Level Security (RLS)
alter table public . projects enable row level security ;
create policy "Users can only access their own projects"
on public . projects for all
using ( auth . uid () = user_id);
RLSの設定はAntigravityが自動生成しますが、必ずビジネスロジックとの整合性を確認すること が重要です。
Phase 3: コア機能実装フェーズ(Day 31〜60)
Antigravityによるコア機能の高速実装
このフェーズが最もAntigravityの力が活きる期間です。以下の方針で実装を進めます。
方針1: 一度に1機能を完成させる
複数の機能を並行して作ると、Antigravityのコンテキストが分散します。「今日はダッシュボードの一覧表示を完璧に仕上げる」という集中が効果的です。
方針2: Antigravityに実装計画を先に立てさせる
[機能名]を実装します。実装を始める前に:
1. 影響するファイル一覧
2. 実装ステップ(順序付き)
3. 考慮すべきエッジケース
4. テスト方針
を先に提示してください。確認後に実装を開始します。
この「計画先行」のひと手間は数分で終わりますが、影響範囲を読み違えたまま実装に入る事故をかなりの割合で防げます。削減率を数値で語れるほどの計測はしていないため、ここでは「実装前に必ず一度立ち止まる」という運用規律として書いています。
方針3: エラーが出たら即座にAntigravityに貼り付ける
エラーメッセージをそのままAntigravityのチャットに貼ると、原因分析と修正案を即座に提示します。「なぜこのエラーが発生したか」の説明も得られるため、スキルアップも同時に行えます。
実装すべきコア機能の優先順位
MUST(MVP必須):
ユーザー認証・プロフィール
コアとなるデータ作成・閲覧・編集・削除(CRUD)
基本的なダッシュボード
Stripeサブスクリプション(次フェーズ)
SHOULD(初回リリースに含めたい):
メール通知
データのエクスポート
簡易レポート・統計表示
NICE TO HAVE(v2以降):
チームコラボレーション
API連携
高度な分析機能
MVPの失敗の多くは「NICE TO HAVEをMUSTと勘違いして開発が遅れる」ことです。Antigravityが速く作れる分だけ、この誘惑は強まります。スコープを守ることが最重要の規律 です。
Day 45〜55: UIポリッシュとレスポンシブ対応
Antigravityを使ったUIの改善では、スクリーンショットとコードを同時に貼り付けての指示が効果的です(マルチモーダル機能)。
このスクリーンショット[貼り付け]のUIを改善してください。
問題点:
- モバイルでテーブルが見切れる
- ダークモード時のコントラストが低い
- ローディング状態の表示がない
shadcn/uiのコンポーネントを活用してください。
Phase 4: Stripe課金実装フェーズ(Day 56〜70)
Stripe統合の全体設計
課金実装は多くの個人開発者が躊躇する部分ですが、Antigravityがあれば恐れることはありません。以下の設計でAntigravityに実装を依頼します。
Next.js 15 + Stripe + Supabaseで以下の課金フローを実装してください:
- Stripeサブスクリプション(月額プラン)
- チェックアウトセッション作成(/api/checkout)
- Webhook処理(/api/webhooks/stripe)でサブスクリプション状態をSupabaseに保存
- ユーザーのサブスクリプション状態に基づいた機能制限
- カスタマーポータル(プラン変更・解約)
TypeScript + Stripe SDK v16を使用
重要な実装ポイント
// src/app/api/webhooks/stripe/route.ts
import { stripe } from '@/lib/stripe' ;
import { createClient } from '@/lib/supabase/server' ;
import { headers } from 'next/headers' ;
export async function POST ( req : Request ) {
const body = await req. text ();
const headersList = await headers ();
// Stripe が送ってくるヘッダー名は stripe-signature(環境変数名と混同しやすい)
const signature = headersList. get ( 'stripe-signature' ) ?? '' ;
let event;
try {
event = stripe.webhooks. constructEvent (
body,
signature,
process.env. STRIPE_WEBHOOK_SECRET !
);
} catch (err) {
return new Response ( `Webhook Error: ${ err }` , { status: 400 });
}
const supabase = await createClient ();
switch (event.type) {
case 'customer.subscription.created' :
case 'customer.subscription.updated' : {
const subscription = event.data.object;
// サブスクリプション状態をSupabaseに同期
await supabase
. from ( 'subscriptions' )
. upsert ({
stripe_subscription_id: subscription.id,
stripe_customer_id: subscription.customer as string ,
status: subscription.status,
price_id: subscription.items.data[ 0 ].price.id,
// 期間終了日の格納位置は API バージョンで変わることがあります。
// 初回は console.log(subscription) で実レスポンスを確認してから確定させてください。
current_period_end: new Date (subscription.current_period_end * 1000 ). toISOString (),
});
break ;
}
case 'customer.subscription.deleted' : {
const subscription = event.data.object;
await supabase
. from ( 'subscriptions' )
. update ({ status: 'canceled' })
. eq ( 'stripe_subscription_id' , subscription.id);
break ;
}
}
return new Response ( null , { status: 200 });
}
Webhookの実装はAntigravityが生成しますが、ローカルでのWebhookテストは必ずStripe CLIで行う ことを徹底します。
Webhookが一件も処理されないときに最初に見る場所
課金まわりで一番多い停止のしかたは、「エラーが出ない停止」です。画面は動き、Checkout も完了し、Stripe 側にはサブスクリプションが作られます。それなのにデータベースの subscriptions テーブルだけが空のまま、という状態になります。
原因の筆頭は、署名検証で読み取るヘッダー名の取り違えです。Stripe が送ってくるヘッダーは stripe-signature ですが、検証に使う秘密鍵の環境変数が STRIPE_WEBHOOK_SECRET であるため、ヘッダー名のほうを stripe-webhook-secret と書いてしまう取り違えが起こります。存在しないヘッダーを読むと空文字が返り、constructEvent は必ず例外を投げ、ハンドラは 400 を返して終わります。
どの程度「必ず」なのかを、Stripe の署名方式(t=<UNIXタイムスタンプ>,v1=<HMAC-SHA256(タイムスタンプ + "." + 生ボディ)>)を最小構成で再現して確かめました。
// verify-header-name.mjs — 署名検証がヘッダー名でどう変わるかを確認する
import crypto from 'node:crypto' ;
const secret = 'whsec_test_dummy_value' ;
const payload = JSON . stringify ({ id: 'evt_1' , type: 'customer.subscription.created' });
const ts = Math. floor (Date. now () / 1000 );
const sig = crypto. createHmac ( 'sha256' , secret). update ( `${ ts }.${ payload }` ). digest ( 'hex' );
// Stripe が実際に付与するヘッダー
const headers = new Headers ({ 'stripe-signature' : `t=${ ts },v1=${ sig }` });
function verify ( raw , header , secret ) {
if ( ! header) throw new Error ( 'No signatures found matching the expected signature for payload.' );
const parts = Object. fromEntries (header. split ( ',' ). map (( p ) => p. split ( '=' )));
const expected = crypto. createHmac ( 'sha256' , secret). update ( `${ parts . t }.${ raw }` ). digest ( 'hex' );
if (expected !== parts.v1) throw new Error ( 'signature mismatch' );
return JSON . parse (raw);
}
for ( const name of [ 'stripe-webhook-secret' , 'stripe-signature' ]) {
const h = headers. get (name) ?? '' ;
try {
console. log (name, '->' , verify (payload, h, secret).type);
} catch (e) {
console. log (name, '-> THROW:' , e.message);
}
}
手元の Node.js v22 で実行した出力です。
stripe-webhook-secret -> THROW: No signatures found matching the expected signature for payload.
stripe-signature -> customer.subscription.created
取り違えた側は 100% 落ちます。裏を返せば、Stripe ダッシュボードのイベントログで対象エンドポイントが軒並み 400 を返しているなら、ネットワークやデータベースを疑う前にヘッダー名を1行見れば済みます。
私はこの3点の確認を、課金コードを本番に載せる前の手順として固定しています。生成されたコードの読みやすさに安心してしまう場面が多いので、目で追う対象をあらかじめ決めておくほうが確実だと考えているためです。
確認項目 正しい値 取り違えの症状
読み取るヘッダー名 stripe-signature全イベントが 400・DBは空のまま
検証に渡すボディ await req.text()(生文字列)署名不一致。req.json() を通すと再直列化で崩れる
秘密鍵 STRIPE_WEBHOOK_SECRET(CLI用と本番用は別物)ローカルは通るが本番だけ落ちる
2番目のボディの扱いは特に見落としやすい箇所です。Next.js の App Router では req.json() が使えてしまうため、つい先にパースしたくなります。しかし HMAC はバイト列に対して計算されているため、一度オブジェクトにしてから文字列に戻すと、キーの順序や空白の差で署名が一致しなくなります。生ボディを先に確保し、そのうえで検証を通し、検証済みイベントからパース済みオブジェクトを受け取る、という順序を崩さないでください。
# Stripe CLIのインストールとローカルWebhookテスト
brew install stripe/stripe-cli/stripe
stripe login
stripe listen --forward-to localhost:3000/api/webhooks/stripe
プライシング戦略
個人開発SaaSの課金設計で重要なのは、フリープランを「使いたくなるが物足りない」設計にすること です。
Free : コア機能を制限付きで利用可(プロジェクト3件、エクスポート不可)
Pro ¥980/月 : 制限解除、優先サポート、高度な分析機能
Team ¥2,980/月 : 複数人コラボ、チームダッシュボード
AntigravityでのFeature Flag実装により、プランに応じた機能制御を一元管理します。
Phase 5: ベータリリースと初収益フェーズ(Day 71〜90)
ランディングページの作成
Antigravityで高品質なランディングページを迅速に作成できます。
以下の情報をもとに、Next.js + Tailwind CSS + shadcn/uiで
転換率の高いランディングページを作成してください:
- プロダクト名: [名前]
- ターゲット: [具体的な職種/業界]
- 解決する問題: [問題の説明]
- 主要機能3つ: [機能A], [機能B], [機能C]
- 料金プラン: Free / Pro ¥980/月
構成: Hero→問題提起→解決策→機能説明→料金→FAQ→CTA
本番環境へのデプロイ
デプロイ先は、エッジで動かすなら Cloudflare Workers、Next.js の機能をそのまま使いたいなら Vercel が素直です。どちらを選ぶ場合でも、ローカルで通っていた環境変数が本番に入っていない、という一点が最初の詰まりどころになります。Stripe の秘密鍵と Webhook 用の秘密鍵、Supabase の URL とサービスロールキーを、デプロイ前にプラットフォーム側の環境変数画面で1つずつ照合しておいてください。
初期ユーザー獲得戦略
Antigravityを活用したコンテンツマーケティングが効果的です。
1. Product Huntローンチ
AntigravityでProduct Huntの投稿文・コメント返信テンプレートを事前に作成します。
2. ニッチコミュニティへの参加
ターゲットユーザーが集まるSlack/Discord/Redditコミュニティに価値ある情報提供者として参加し、プロダクトを自然に紹介します。
3. コールドアウトリーチ
Antigravityで個人化されたアウトリーチメールを大量生成します。テンプレートではなく、相手のサービス・課題に合わせた内容にすることがポイントです。
4. SEOコンテンツ
Antigravityで「[業界] + [問題] + 解決方法」という検索意図のコンテンツを定期的に発行します。初期は月4〜8本のペースで始め、効果のあるキーワードに絞ります。
Day 80〜90: フィードバックループと改善
初期ユーザーからのフィードバックを収集・分析する際もAntigravityが役立ちます。
以下のユーザーフィードバック[貼り付け]を分析してください:
1. 最も頻繁に言及されている問題TOP3
2. ユーザーが最も価値を感じている機能
3. 次のスプリントで対応すべき改善優先順位
4. チャーンリスクのあるフィードバックの特定
Antigravityが提示する分析をもとに、次の2週間のスプリントを設計します。
よくある失敗パターンとAntigravityによる回避策
失敗1: スコープクリープ(機能追加の誘惑)
Antigravityが速く実装できるからこそ、「ついでにこれも」という誘惑が生まれます。対策として、未実装機能は必ずGitHub Issuesに記録し、Antigravityに「今のスプリントスコープ外」と明示したうえで会話を続けます。
失敗2: 完璧なコードを求めすぎる
Antigravityのコードレビューを求め続けてリリースが遅れるパターンです。MVPのコードは「動けば良い」段階があります。セキュリティクリティカルな部分(認証・課金)以外は、まず動かしてユーザーの反応を見ることを優先します。
失敗3: Antigravityの提案を無批判に受け入れる
Antigravityが生成するコードは優れていますが、ビジネスロジックの整合性やセキュリティ要件はあなた自身が責任を持って確認する必要があります。特にRLS設定、APIキーの管理、Webhookの検証ロジックは必ず手動でレビューします。
失敗4: 一人で抱え込む
Antigravityがいれば技術的な壁は大幅に低くなりましたが、孤独な開発は継続性のリスクです。Xやnoteでの発信、インディーハッカーコミュニティへの参加で、モチベーションを維持するコミュニティを作りましょう。
90日後に期待できる成果
適切に実行した場合の90日後の状態:
プロダクト : 主要機能が動作するMVPがCloudflare Pages上で稼働中
ユーザー : ベータユーザー20〜50人、フリープランユーザー100人以上
収益 : 有料転換率5〜10%なら月額5,000〜50,000円の初収益
学習 : 市場フィードバックに基づいた次の3ヶ月の明確なロードマップ
初収益は小さくて構いません。重要なのは「お金を払ってくれる顧客が存在する」ことが証明されたことです。
収益化の自動化についてはAIを活用した収益化の完全プレイブック で詳しく解説しています。また、サブスクリプション管理の高度な最適化はSupabase完全本番ガイド も参考になります。
まとめ
Antigravityを活用したSaaS MVP開発の90日ロードマップを振り返ると:
Day 1〜14(検証フェーズ) : 問題定義・競合分析・プロトタイプで市場検証
Day 15〜30(基盤フェーズ) : 技術スタック選定・認証・データモデル実装
Day 31〜60(実装フェーズ) : コア機能の高速開発・UIポリッシュ
Day 56〜70(課金フェーズ) : Stripe統合・プライシング設計
Day 71〜90(リリースフェーズ) : ランディングページ・ユーザー獲得・改善
Antigravityが短くしてくれるのは実装にかかる時間であって、「何を作るか」「誰のために作るか」という判断ではありません。この部分は最後まで手放せませんし、手放さないほうが結果的に速く進みます。
そして90日の途中で最も高くつくのは、大きな設計ミスよりも、今日見た stripe-signature のような一行です。画面が動いているぶん気づきにくく、気づかないまま数週間が過ぎます。課金の周辺だけは、生成されたコードをそのまま信じず、手元で一度動かして確かめてから本番に載せてください。
次の一歩として、まずは Phase 1 の Day 1〜3 だけを実行してみてください。コードを書かずに、お金を払ってでも解決したいと言う人を10人探す。ここが埋まらないまま先に進むと、残り87日の実装がすべて仮説の上に乗ることになります。
お読みいただきありがとうございました。私自身まだ試行錯誤の途中ですが、同じ道を進む方の参考になれば嬉しく思います。