ai-saas-mvp-kinde

了解。そのスタックは「軽量・低コスト・高速MVP」にかなり良い構成です。ただし最初に一つだけ現実的な指摘をしておくと、Kinde + Supabase + Stripe + Geminiを“全部同時に完璧統合”しようとすると確実に詰まります
なのでMVPは 段階的に動かす(Auth → DB → AI → 課金) が正解です。

以下に「コピペで進められるレベル」で、CLI中心の完全手順を書きます。


全体構成

Frontend: Astro (Cloudflare Pages)
Auth: Kinde
DB: Supabase (RLS)
Billing: Stripe
AI: Gemini (Google AI Studio)
Backend: Cloudflare Workers

0. 事前準備(アカウント)

作成しておくもの:

  • Cloudflare

  • Kinde

  • Supabase

  • Stripe

  • Google AI Studio(Gemini)


1. プロジェクト作成(Astro + Cloudflare)

CLI

npm create astro@latest ai-saas-mvp-kinde
cd ai-saas-mvp-kinde

# テンプレ
# → minimal
# → TypeScript YES

npm install

Cloudflare用

npm install -D wrangler
npx wrangler login

2. Kinde(認証)

Kinde設定

  • Application作成

ai-sales

いいね、ここは“迷うと一気に止まる”ポイント。
なのでコピペでそのまま進められるCLI手順を出す👇


ゴール

👉 Astro + Clerk + Supabase + Gemini がローカルで動く状態


0. 前提

事前に入れておく👇

node -v   # v18以上
npm -v

① プロジェクト作成(Astro)

npm create astro@latest ai-sales
cd ai-sales
npm install

② 必要ライブラリ

npm install @supabase/supabase-js @google/generative-ai
npm install @clerk/astro

③ Clerkセットアップ

npx clerk@latest init

👉 途中で👇

  • 「Framework」→ Astro

  • 「Use Clerk components」→ Yes


環境変数が自動生成される

.env

PUBLIC_CLERK_PUBLISHABLE_KEY=pk_xxx
CLERK_SECRET_KEY=sk_xxx

④ Supabaseセットアップ

# CLIインストール
npm install -g supabase

# ログイン
supabase login

# 初期化(ローカル設定)
supabase init

プロジェクト作成(Web側)

👉 Supabase で新規プロジェクト作成

ai-sales-mvp

結論から言うと、OpenAI → Geminiへの置き換えは「SDK差し替え+API呼び出し変更」だけです。
構造はそのまま使えます(Astro / Clerk / Supabaseはそのまま)。


🧩 変更ポイント(最小)

やることは3つだけ👇

  1. パッケージ変更

  2. APIキー追加

  3. ai.tsを書き換え


📦 ① Geminiパッケージ追加

npm install @google/generative-ai

🔑 ② 環境変数追加

.env に追加👇

GEMINI_API_KEY=xxx

👉 APIキーは
Google AI Studio で取得


🧠 ③ AIコード差し替え(これが本体)

src/lib/ai.ts を丸ごと置き換え👇

import { GoogleGenerativeAI } from "@google/generative-ai";

const genAI = new GoogleGenerativeAI(
  import.meta.env.GEMINI_API_KEY
);

export async function generateMessage(lead: any) {
  const model = genAI.getGenerativeModel({
    model: "gemini-1.5-flash",
  });

  const prompt = `
あなたは優秀な営業です。
以下の情報から短い営業メールを書いてください。

会社名: ${lead.company}
担当者: ${lead.name}
課題: ${lead.problem}
`;

  const result = await model.generateContent(prompt);
  const response = await result.response;

  return response.text();
}

⚠️ ④ 既存コードはそのままでOK

これらは変更不要👇

顧客獲得自動化

いい視点です。ここを具体化すると一気に“プロダクト化”できます。
ただし先に一つだけ現実を言うと、全部をAPIで自動化すると失敗しやすいです。
👉 「スケールする部分だけAPI化」するのが正解です。


結論:APIで自動化すべき5領域

これだけやれば十分戦えます👇

① リード収集
② データ補完(Enrichment)
③ パーソナライズ生成
④ 送信・フォローアップ
⑤ トラッキング・分析


① リード収集API

ターゲット企業・担当者を取得

代表例:

  • Apollo.io API

  • Clearbit API

  • LinkedIn(※公式API制限あり)

やること:

  • 業界 / 従業員数 / 地域でフィルタ

  • メール・役職取得

👉 実装イメージ

const leads = await fetch("https://api.apollo.io/v1/mixed_people/search", {
  method: "POST",
  headers: { "X-Api-Key": process.env.APOLLO_KEY },
  body: JSON.stringify({
    organization_num_employees_ranges: ["10,200"],
    titles: ["BDR", "Sales"],
  }),
})

② データ補完API(Enrichment)

情報を“営業で使える状態”にする

  • 会社URL

  • 最近のニュース

  • 技術スタック

代表:

  • Clearbit

  • Crunchbase

👉 ここが重要
「この会社に何が刺さるか」をAIに渡す材料になる


③ AIパーソナライズ生成API

ここが“返信率を決めるコア”

代表:

  • OpenAI API

  • Anthropic API

やること:

リード探し


🧠 ヒーローセクション(ファーストビュー)

コールドアウトリーチを、商談獲得に変える。

AIが見込み顧客を見つけ、返信が来るメールを自動生成。
営業チームを増やさずに、パイプラインを拡大できます。

今すぐ無料で開始(クレジットカード不要)

👉 CTA
無料で20リードを試す →


⚡ サブコピー(信頼+共感)

“ツール”ではなく、“成果”のために設計されています。

見込み顧客の発見からメール作成までを自動化し、
BDRが本来やるべき「商談獲得」に集中できます。


🔥 ベネフィット(3つに絞る)

🚀 数分で高品質なリードを獲得
手作業のリサーチは不要。今すぐアプローチ可能なリストを生成。

✉️ 返信が来るメールを自動作成
高コンバージョンのパターンをもとにAIが生成。

📈 人を増やさずに売上を伸ばす
チームの人数を増やさず、パイプラインを拡大。


💰 価格セクション

成果に応じてスケールする、シンプルな料金体系。

Starter — $29 / 月
・リード100件 / 月
・追加:$0.05 / リード

Growth(人気)— $99 / 月
・リード1,000件 / 月
・追加:$0.03 / リード

Pro — $299 / 月
・リード5,000件 / 月
・追加:$0.02 / リード

+ チームメンバー:$20 / 人 / 月

Enterprise — 要問い合わせ
・大規模対応 / 専用サポート

完全Billing実装

いいですね。ただし一つだけ現実的な前提を置きます。
👉 完全コピペで“そのまま動く”には最低限の環境変数設定は必要です。
それを含めて、**Cloudflare Workersベースの最小SaaS(Clerk + Supabase + Stripe)**を出します。


🧠 全体構成(再確認)

  • Front:Cloudflare Pages(静的HTML)

  • API:Cloudflare Workers

  • Auth:Clerk(フロントでJWT取得 → Workerで検証)

  • DB:Supabase(REST)

  • Billing:Stripe(Checkout + Webhook)


📁 構成

/
  worker/
    src/index.ts
  public/
    index.html
  wrangler.toml

⚙️ wrangler.toml

name = "saas-worker"
main = "worker/src/index.ts"
compatibility_date = "2024-12-01"

[vars]
SUPABASE_URL = "https://xxxx.supabase.co"
SUPABASE_SERVICE_ROLE_KEY = "xxxx"

STRIPE_SECRET_KEY = "sk_test_xxx"
STRIPE_WEBHOOK_SECRET = "whsec_xxx"

🧱 Worker本体(API)

// worker/src/index.ts

export default {
  async fetch(req: Request, env: Env) {
    const url = new URL(req.url);

    // ===== Auth付きユーザー取得 =====
    if (url.pathname === "/me") {
      const userId = await verifyClerk(req);

      if (!userId) {
        return new Response("Unauthorized", { status: 401 });
      }

      const res = await fetch(
        `${env.SUPABASE_URL}/rest/v1/users?id=eq.${userId}`,
        {
          headers: {
            apikey: env.SUPABASE_SERVICE_ROLE_KEY,
            Authorization: `Bearer ${env.SUPABASE_SERVICE_ROLE_KEY}`
          }
        }
      );

      return new Response(await res.text(), { status: 200 });
    }

    // ===== Stripe Checkout作成 =====
    if (url.pathname === "/create-checkout") {
      const userId = await verifyClerk(req);

      const body = await fetch("https://api.stripe.com/v1/checkout/sessions", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${env.STRIPE_SECRET_KEY}`,
          "Content-Type": "application/x-www-form-urlencoded"
        },
        body: new URLSearchParams({
          mode: "subscription",
          "line_items[0][price]": "price_xxx",
          "line_items[0][quantity]": "1",
          success_url: "https://yourdomain.com/success",
          cancel_url: "https://yourdomain.com"
        })
      });

      return new Response(await body.text());
    }

    // ===== Webhook =====
    if (url.pathname === "/webhook") {
      const event = await req.json();

      if (event.type === "checkout.session.completed") {
        const subId = event.data.object.subscription;
        const userId = event.data.object.metadata.userId;

        await fetch(`${env.SUPABASE_URL}/rest/v1/users`, {
          method: "POST",
          headers: {
            apikey: env.SUPABASE_SERVICE_ROLE_KEY,
            Authorization: `Bearer ${env.SUPABASE_SERVICE_ROLE_KEY}`,
            "Content-Type": "application/json",
            Prefer: "resolution=merge-duplicates"
          },
          body: JSON.stringify({
            id: userId,
            plan: "pro",
            subscription_id: subId,
            billing_status: "active"
          })
        });
      }

      return new Response("ok");
    }

    return new Response("ok");
  }
};

// ===== Clerk JWT検証(簡易) =====
async function verifyClerk(req: Request): Promise<string | null> {
  const token = req.headers.get("Authorization")?.replace("Bearer ", "");

  if (!token) return null;

  // 本番はJWT検証する(ここは簡易)
  const payload = JSON.parse(atob(token.split(".")[1]));

  return payload.sub;
}

🌐 フロント(最小)

<!-- public/index.html -->
<!DOCTYPE html>
<html>
<head>
  <title>SaaS MVP</title>
</head>
<body>
  <h1>AI Lead Generator</h1>

  <button onclick="checkout()">Upgrade</button>

  <script>
    async function checkout() {
      const res = await fetch("/create-checkout");
      const data = await res.json();
      window.location = data.url;
    }
  </script>
</body>
</html>

🗄 Supabaseテーブル

create table users (
  id text primary key,
  plan text,
  billing_status text,
  subscription_id text
);

🔐 最低限やるべき設定

① Clerk

  • フロントでJWT取得

Cloudflare Pages + Astro + React

React とは

Meta(旧Facebook)が開発したJavaScriptライブラリで、UIを構築するために使われます。


核心的な考え方

コンポーネントベース

UIを小さな部品(コンポーネント)に分割して組み合わせます。

// ボタンコンポーネント
function Button({ label, onClick }) {
  return <button onClick={onClick}>{label}</button>;
}

// 上のコンポーネントを使う
function App() {
  return <Button label="クリック" onClick={() => alert('押した!')} />;
}

宣言的UI

「どう変えるか」ではなく「どう見えるべきか」を書くだけでOKです。

// stateが変わると自動的に画面が更新される
function Counter() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

主な特徴

JSX — JavaScriptの中にHTMLのような構文が書ける

State(状態管理) — データが変わると画面が自動更新される

Virtual DOM — 差分だけを効率よく更新するので高速

豊富なエコシステム — ライブラリや情報が非常に多い


Astroの文脈でのReact

Astroは基本的にサーバーでHTMLを生成します。Reactはインタラクティブな部分だけに使うイメージです。

Astroページ(静的)
  └── Reactコンポーネント(ボタン、フォーム、アニメーションなど動く部分)

静的な部分はReact不要、動きが必要な部分だけReactを使えるので、軽くて速いサイトが作れます。


Cloudflare Pages + Astro + React のセットアップ方法をご説明します。

SaaSのMVP

AI活用による新規顧客獲得SaaSのMVP(Minimum Viable Product)を構築するための手順を、コマンド操作中心にまとめました。

Cloudflare環境(Astro + Clerk + Supabase + Stripe)での実装は、エッジコンピューティングの恩恵を受けられる非常に強力な構成です。


1. プロジェクトの初期化と依存関係のインストール

まずはベースとなるAstroプロジェクトを作成し、必要なSDKをインストールします。

Bash

# Astroプロジェクトの作成 (すべてデフォルト/yでOK)
npm create astro@latest my-ai-saas -- --template minimal
cd my-ai-saas

# 必要なパッケージのインストール
npm install @clerk/astro @supabase/supabase-js stripe @google/generative-ai
npm install -D wrangler # Cloudflareデプロイ用

2. Cloudflare Pages & データベースの準備

Cloudflareでのホスティング設定と、環境変数のための.envファイルを作成します。

Bash

# Cloudflare Adapterの追加
npx astro add cloudflare

# 環境変数の雛形作成
touch .env

.envファイルに以下のキーを準備してください(各管理画面から取得):

  • CLERK_PUBLISHABLE_KEY / CLERK_SECRET_KEY

  • SUPABASE_URL / SUPABASE_ANON_KEY

  • STRIPE_SECRET_KEY

  • GEMINI_API_KEY


3. 各サービスの設定手順

Clerk (認証)

astro.config.mjs にClerkのインテグレーションを追加します。

Stripe CLIでのローカルテスト方法

Stripe CLIでのローカルテスト方法

1. Webhookリスナーの起動

stripe listen --forward-to localhost:4321/api/webhook

実行すると以下のようなWebhook署名シークレットが表示されます:

> Ready! Your webhook signing secret is whsec_xxxxxxxxxxxxxxxxxx (^C to quit)

このwhsec_....envファイルに追加してください:

STRIPE_SECRET_KEY=sk_test_xxxxxxxxxx
STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxx

2. Checkoutセッション作成エンドポイント

src/pages/api/checkout.ts

import type { APIRoute } from "astro";
import Stripe from "stripe";

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

export const POST: APIRoute = async ({ request }) => {
  const { priceId } = await request.json();

  const session = await stripe.checkout.sessions.create({
    mode: "subscription",
    payment_method_types: ["card"],
    line_items: [{ price: priceId, quantity: 1 }],
    success_url: "http://localhost:4321/success",
    cancel_url: "http://localhost:4321/cancel",
  });

  return new Response(JSON.stringify({ url: session.url }), { status: 200 });
};

3. Webhookエンドポイント

src/pages/api/webhook.ts

import type { APIRoute } from "astro";
import Stripe from "stripe";

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

export const POST: APIRoute = async ({ request }) => {
  const body = await request.text();
  const sig = request.headers.get("stripe-signature")!;

  let event;
  try {
    event = stripe.webhooks.constructEvent(
      body,
      sig,
      process.env.STRIPE_WEBHOOK_SECRET!
    );
  } catch (err) {
    return new Response(`Webhook Error: ${err}`, { status: 400 });
  }

  // イベント処理
  switch (event.type) {
    case "checkout.session.completed":
      const session = event.data.object;
      // サブスクリプション開始処理(DBへの保存など)
      console.log("サブスクリプション開始:", session.customer);
      break;

    case "customer.subscription.deleted":
      // サブスクリプション解約処理
      console.log("サブスクリプション解約");
      break;
  }

  return new Response(JSON.stringify({ received: true }), { status: 200 });
};

4. テストの流れ

ターミナルを2つ用意します:

新規顧客獲得SaaSのMVP

AI活用による新規顧客獲得SaaSのMVP(Minimum Viable Product)を構築するための手順を、コマンド操作中心にまとめました。

Cloudflare環境(Astro + Clerk + Supabase + Stripe)での実装は、エッジコンピューティングの恩恵を受けられる非常に強力な構成です。


1. プロジェクトの初期化と依存関係のインストール

まずはベースとなるAstroプロジェクトを作成し、必要なSDKをインストールします。

Bash

# Astroプロジェクトの作成 (すべてデフォルト/yでOK)
npm create astro@latest my-ai-saas -- --template minimal
cd my-ai-saas

# 必要なパッケージのインストール
npm install @clerk/astro @supabase/supabase-js stripe @google/generative-ai
npm install -D wrangler # Cloudflareデプロイ用

2. Cloudflare Pages & データベースの準備

Cloudflareでのホスティング設定と、環境変数のための.envファイルを作成します。

Bash

# Cloudflare Adapterの追加
npx astro add cloudflare

# 環境変数の雛形作成
touch .env

.envファイルに以下のキーを準備してください(各管理画面から取得):

  • CLERK_PUBLISHABLE_KEY / CLERK_SECRET_KEY

  • SUPABASE_URL / SUPABASE_ANON_KEY

  • STRIPE_SECRET_KEY

  • GEMINI_API_KEY


3. 各サービスの設定手順

Clerk (認証)

astro.config.mjs にClerkのインテグレーションを追加します。