mcpField
N
nextjs-supabase-saas-planner

nextjs-supabase-saas-planner

github.com/sitechfromgeorgia開発36 回インストール
リポジトリ

nextjs-supabase-saas-planner について

Next.js 15 と Supabase を使うチーム向けに、SaaS の構想を技術計画にまとめるスキルです。アーキテクチャ、データ、認証、課金、段階的なロードマップを扱います。

主な機能

  1. 01アーキテクチャと技術スタックの計画
  2. 02データベーススキーマと RLS パターン
  3. 03認証とマルチテナント設計
  4. 04課金連携の計画
  5. 05MVP の優先順位とローンチロードマップ

活用シーン

  1. 01SaaS の構想を技術仕様に変換
  2. 02Supabase のデータモデルとアクセス制御を計画
  3. 03組織単位のテナント分離を設計
  4. 04MVP の範囲とリリース節目を決定

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/sitechfromgeorgia/georgian-distribution-system --skill nextjs-supabase-saas-planner

インストール後、Claude Code で/nextjs-supabase-saas-plannerを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

README.md
references/
AUTHENTICATION_PATTERNS.md
BILLING_PATTERNS.md

詳細

Next.js + Supabase SaaS プランナー

概要

プロダクトのアイデアを実行可能な技術ロードマップに変換する、SaaS 計画のエキスパートアシスタントです。Next.js 15 + Supabase スタックを専門とし、アーキテクチャ設計、データベーススキーマ、認証パターン、マルチテナンシー、請求の統合、ローンチ戦略を扱います。

このスキルを使用する場面:

  • 新しい SaaS プロダクトをゼロから計画するとき
  • プロダクトのアイデアを技術仕様に落とし込むとき
  • SaaS アプリケーションのデータベーススキーマを設計するとき
  • 認証と認可を設定するとき
  • マルチテナントアーキテクチャを実装するとき
  • サブスクリプション請求(Stripe、Paddle)を統合するとき
  • 機能のリリースと MVP の優先順位を計画するとき
  • 開発ロードマップとスケジュールを作成するとき

計画ワークフロー

フェーズ 1:要件の確認と整理

まず、次の質問をします:

  1. プロダクトの概要

    • SaaS はどのような問題を解決しますか?
    • ターゲットユーザーは誰ですか?
    • 独自の価値提案は何ですか?
  2. 想定規模

    • 初年度のユーザー数はどのくらいですか?(0~1K、1K~10K、10K~100K、100K+)
    • 想定される成長率はどのくらいですか?
    • 地理的な分布はどうなりますか?
  3. チームとスケジュール

    • チームの人数とスキルは?
    • ローンチの予定は?(MVP の日付)
    • 予算の制約はありますか?
  4. ビジネスモデル

    • 料金プランは?
    • 無料トライアルですか、それともフリーミアムですか?
    • 有料プランを差別化する機能は何ですか?
  5. コア機能

    • MVP に必須の機能は?
    • v1.1 であると望ましい機能は?
    • 今後のロードマップに含めるアイデアは?
フェーズ 2:技術アーキテクチャの計画

要件に基づいて、以下を作成します:

  1. システムアーキテクチャ図
  2. データベーススキーマ
  3. ファイル/フォルダー構成
  4. 認証フロー
  5. マルチテナンシー戦略(該当する場合)
  6. 請求統合計画
  7. デプロイアーキテクチャ
フェーズ 3:開発ロードマップ

週ごとの計画を作成します:

  • 第 1~2 週:セットアップとインフラストラクチャ
  • 第 3~4 週:コア機能
  • 第 5~6 週:認証と請求
  • 第 7~8 週:仕上げとテスト
  • 第 9 週:ローンチ準備

Next.js 15 + Supabase スタック(2025)

推奨技術スタック
Frontend:
├─ Next.js 15 (App Router)
├─ React 19
├─ TypeScript
├─ Tailwind CSS
└─ shadcn/ui components

Backend:
├─ Next.js API Routes (Server Actions)
├─ Supabase Edge Functions (when needed)
└─ Stripe/Paddle webhooks

Database:
├─ PostgreSQL (Supabase)
├─ Row Level Security (RLS)
└─ Supabase Realtime (optional)

Authentication:
├─ Supabase Auth
├─ OAuth providers (Google, GitHub, etc.)
└─ Magic links

Storage:
└─ Supabase Storage (files, images)

Deployment:
├─ Vercel (frontend + API)
├─ Supabase (database, auth, storage)
└─ Cloudflare (CDN, DNS)

Payments:
├─ Stripe (recommended)
└─ Paddle (alternative)

Monitoring:
├─ Vercel Analytics
├─ Sentry (error tracking)
└─ PostHog (product analytics - optional)
このスタックを選ぶ理由

Next.js 15:

  • パフォーマンスを高めるサーバーコンポーネント
  • データ更新に使えるサーバーアクション
  • 組み込みのキャッシュ戦略
  • 優れた DX と迅速な反復開発

Supabase:

  • すぐに使える REST API
  • リアルタイムサブスクリプション
  • 組み込みの認証機能
  • 行レベルセキュリティ
  • オープンソース(ベンダーロックインのリスクなし)

TypeScript:

  • 型安全性によるバグの削減
  • IDE のサポート向上
  • コードの自己文書化

Stripe:

  • 決済業界の標準
  • 優れたドキュメント
  • サブスクリプション管理
  • 請求書の処理

ファイル構成テンプレート

my-saas/
├─ app/
│  ├─ (auth)/
│  │  ├─ login/
│  │  │  └─ page.tsx
│  │  ├─ signup/
│  │  │  └─ page.tsx
│  │  ├─ forgot-password/
│  │  │  └─ page.tsx
│  │  └─ layout.tsx
│  │
│  ├─ (marketing)/
│  │  ├─ page.tsx                 # Landing page
│  │  ├─ pricing/
│  │  │  └─ page.tsx
│  │  ├─ about/
│  │  │  └─ page.tsx
│  │  └─ layout.tsx
│  │
│  ├─ (dashboard)/
│  │  ├─ dashboard/
│  │  │  └─ page.tsx
│  │  ├─ settings/
│  │  │  ├─ profile/
│  │  │  │  └─ page.tsx
│  │  │  ├─ billing/
│  │  │  │  └─ page.tsx
│  │  │  └─ team/
│  │  │     └─ page.tsx
│  │  └─ layout.tsx
│  │
│  ├─ api/
│  │  ├─ webhooks/
│  │  │  └─ stripe/
│  │  │     └─ route.ts
│  │  └─ og/                     # Open Graph images
│  │     └─ route.tsx
│  │
│  ├─ layout.tsx                  # Root layout
│  └─ globals.css
│
├─ components/
│  ├─ ui/                         # shadcn components
│  │  ├─ button.tsx
│  │  ├─ card.tsx
│  │  ├─ dialog.tsx
│  │  └─ ...
│  ├─ auth/
│  │  ├─ login-form.tsx
│  │  └─ signup-form.tsx
│  ├─ dashboard/
│  │  ├─ sidebar.tsx
│  │  └─ navbar.tsx
│  ├─ marketing/
│  │  ├─ hero.tsx
│  │  ├─ features.tsx
│  │  └─ pricing-cards.tsx
│  └─ shared/
│     ├─ user-avatar.tsx
│     └─ loading-spinner.tsx
│
├─ lib/
│  ├─ supabase/
│  │  ├─ client.ts              # Client-side Supabase
│  │  ├─ server.ts              # Server-side Supabase
│  │  └─ middleware.ts          # Auth middleware
│  ├─ stripe/
│  │  ├─ client.ts
│  │  └─ server.ts
│  ├─ db/
│  │  └─ queries.ts             # Database queries
│  ├─ auth.ts                   # Auth helpers
│  └─ utils.ts                  # General utilities
│
├─ hooks/
│  ├─ use-user.ts               # User session hook
│  ├─ use-subscription.ts       # Subscription status
│  └─ use-toast.ts
│
├─ actions/                      # Server Actions
│  ├─ auth/
│  │  ├─ login.ts
│  │  ├─ signup.ts
│  │  └─ logout.ts
│  ├─ billing/
│  │  ├─ create-checkout.ts
│  │  └─ cancel-subscription.ts
│  └─ profile/
│     └─ update-profile.ts
│
├─ types/
│  ├─ database.ts               # Generated from Supabase
│  ├─ supabase.ts
│  └─ stripe.ts
│
├─ supabase/
│  ├─ migrations/               # Database migrations
│  │  └─ 20250101000000_initial.sql
│  ├─ functions/                # Edge Functions (optional)
│  └─ config.toml
│
├─ public/
│  ├─ images/
│  └─ icons/
│
├─ middleware.ts                 # Next.js middleware (auth)
├─ next.config.js
├─ tailwind.config.js
├─ tsconfig.json
├─ package.json
└─ .env.local

データベーススキーマのパターン

SaaS の基本テーブル
-- Enable UUID extension
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

-- Users table (extended from Supabase auth.users)
CREATE TABLE profiles (
  id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
  email TEXT UNIQUE NOT NULL,
  full_name TEXT,
  avatar_url TEXT,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Organizations/Teams (multi-tenant)
CREATE TABLE organizations (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  name TEXT NOT NULL,
  slug TEXT UNIQUE NOT NULL,
  logo_url TEXT,
  subscription_tier TEXT DEFAULT 'free',
  subscription_status TEXT DEFAULT 'active',
  stripe_customer_id TEXT UNIQUE,
  stripe_subscription_id TEXT UNIQUE,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Organization memberships
CREATE TABLE organization_members (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
  user_id UUID REFERENCES profiles(id) ON DELETE CASCADE,
  role TEXT NOT NULL DEFAULT 'member', -- 'owner', 'admin', 'member'
  invited_by UUID REFERENCES profiles(id),
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  UNIQUE(organization_id, user_id)
);

-- Subscription plans
CREATE TABLE plans (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  name TEXT NOT NULL,
  description TEXT,
  price_monthly DECIMAL(10,2),
  price_yearly DECIMAL(10,2),
  stripe_price_id_monthly TEXT,
  stripe_price_id_yearly TEXT,
  features JSONB,
  limits JSONB, -- { "users": 5, "projects": 10 }
  is_active BOOLEAN DEFAULT true,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Subscription usage tracking
CREATE TABLE usage_records (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
  metric TEXT NOT NULL, -- 'api_calls', 'storage_mb', 'users'
  value INTEGER NOT NULL,
  period_start DATE NOT NULL,
  period_end DATE NOT NULL,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Invitations
CREATE TABLE invitations (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
  email TEXT NOT NULL,
  role TEXT NOT NULL DEFAULT 'member',
  token TEXT UNIQUE NOT NULL,
  invited_by UUID REFERENCES profiles(id),
  expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
  accepted_at TIMESTAMP WITH TIME ZONE,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Audit log (optional but recommended)
CREATE TABLE audit_logs (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  organization_id UUID REFERENCES organizations(id),
  user_id UUID REFERENCES profiles(id),
  action TEXT NOT NULL,
  resource_type TEXT,
  resource_id UUID,
  metadata JSONB,
  ip_address INET,
  user_agent TEXT,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Indexes
CREATE INDEX idx_org_members_org ON organization_members(organization_id);
CREATE INDEX idx_org_members_user ON organization_members(user_id);
CREATE INDEX idx_usage_org_period ON usage_records(organization_id, period_start, period_end);
CREATE INDEX idx_invitations_token ON invitations(token);
CREATE INDEX idx_audit_logs_org ON audit_logs(organization_id);
行レベルセキュリティ(RLS)ポリシー
-- Enable RLS
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE organizations ENABLE ROW LEVEL SECURITY;
ALTER TABLE organization_members ENABLE ROW LEVEL SECURITY;

-- Profiles: Users can read and update their own profile
CREATE POLICY "Users can view own profile"
  ON profiles FOR SELECT
  USING (auth.uid() = id);

CREATE POLICY "Users can update own profile"
  ON profiles FOR UPDATE
  USING (auth.uid() = id);

-- Organizations: Members can read their organizations
CREATE POLICY "Members can view their organizations"
  ON organizations FOR SELECT
  USING (
    id IN (
      SELECT organization_id 
      FROM organization_members 
      WHERE user_id = auth.uid()
    )
  );

-- Only owners can update organizations
CREATE POLICY "Owners can update organizations"
  ON organizations FOR UPDATE
  USING (
    id IN (
      SELECT organization_id 
      FROM organization_members 
      WHERE user_id = auth.uid() AND role = 'owner'
    )
  );

-- Organization members: Members can view other members
CREATE POLICY "Members can view team members"
  ON organization_members FOR SELECT
  USING (
    organization_id IN (
      SELECT organization_id 
      FROM organization_members 
      WHERE user_id = auth.uid()
    )
  );

認証の設定

Supabase Auth の設定
// lib/supabase/client.ts
import { createBrowserClient } from '@supabase/ssr';

export function createClient() {
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  );
}

// lib/supabase/server.ts
import { createServerClient, type CookieOptions } 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: {
        get(name: string) {
          return cookieStore.get(name)?.value;
        },
        set(name: string, value: string, options: CookieOptions) {
          cookieStore.set({ name, value, ...options });
        },
        remove(name: string, options: CookieOptions) {
          cookieStore.set({ name, value: '', ...options });
        },
      },
    }
  );
}
認証ミドルウェア
// middleware.ts
import { createServerClient, type CookieOptions } from '@supabase/ssr';
import { NextResponse, type NextRequest } from 'next/server';

export async function middleware(request: NextRequest) {
  let response = NextResponse.next({
    request: {
      headers: request.headers,
    },
  });

  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        get(name: string) {
          return request.cookies.get(name)?.value;
        },
        set(name: string, value: string, options: CookieOptions) {
          response.cookies.set({
            name,
            value,
            ...options,
          });
        },
        remove(name: string, options: CookieOptions) {
          response.cookies.set({
            name,
            value: '',
            ...options,
          });
        },
      },
    }
  );

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

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

  // Redirect to dashboard if already authenticated
  if (user && (
    request.nextUrl.pathname === '/login' ||
    request.nextUrl.pathname === '/signup'
  )) {
    return NextResponse.redirect(new URL('/dashboard', request.url));
  }

  return response;
}

export const config = {
  matcher: [
    '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
  ],
};

Stripe の統合

セットアップ
// lib/stripe/server.ts
import Stripe from 'stripe';

export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2024-11-20.acacia',
});

// Create checkout session
export async function createCheckoutSession(
  organizationId: string,
  priceId: string,
  userId: string
) {
  const session = await stripe.checkout.sessions.create({
    mode: 'subscription',
    payment_method_types: ['card'],
    line_items: [
      {
        price: priceId,
        quantity: 1,
      },
    ],
    success_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard/billing?success=true`,
    cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/pricing`,
    client_reference_id: organizationId,
    metadata: {
      organizationId,
      userId,
    },
  });

  return session;
}
Webhook ハンドラー
// app/api/webhooks/stripe/route.ts
import { headers } from 'next/headers';
import { NextResponse } from 'next/server';
import Stripe from 'stripe';
import { stripe } from '@/lib/stripe/server';
import { createClient } from '@/lib/supabase/server';

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

  let event: Stripe.Event;

  try {
    event = stripe.webhooks.constructEvent(
      body,
      signature,
      process.env.STRIPE_WEBHOOK_SECRET!
    );
  } catch (err) {
    return NextResponse.json({ error: 'Invalid signature' }, { status: 400 });
  }

  const supabase = await createClient();

  switch (event.type) {
    case 'checkout.session.completed':
      const session = event.data.object as Stripe.Checkout.Session;
      
      await supabase
        .from('organizations')
        .update({
          stripe_customer_id: session.customer as string,
          stripe_subscription_id: session.subscription as string,
          subscription_status: 'active',
        })
        .eq('id', session.metadata?.organizationId);
      break;

    case 'customer.subscription.updated':
      const subscription = event.data.object as Stripe.Subscription;
      
      await supabase
        .from('organizations')
        .update({
          subscription_status: subscription.status,
        })
        .eq('stripe_subscription_id', subscription.id);
      break;

    case 'customer.subscription.deleted':
      const deletedSub = event.data.object as Stripe.Subscription;
      
      await supabase
        .from('organizations')
        .update({
          subscription_status: 'canceled',
          subscription_tier: 'free',
        })
        .eq('stripe_subscription_id', deletedSub.id);
      break;
  }

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

マルチテナンシーのパターン

組織ベース(推奨)

パターン: 各組織がそれぞれ独自のデータ領域を持ちます。

メリット:

  • データを明確に分離できる
  • 実装が容易
  • 組織ごとにスケーリングできる
  • 組織ごとの請求がシンプル

実装:

// All queries include organization_id
const { data } = await supabase
  .from('projects')
  .select('*')
  .eq('organization_id', currentOrgId);
サブドメインベース(上級)

パターン: 各組織が独自のサブドメインを持ちます(例:acme.yourapp.com)。

メリット:

  • ブランディングの向上
  • 明確な分離
  • プロフェッショナルな外観

課題:

  • DNS の管理
  • SSL 証明書
  • ルーティングの複雑化

実装:

// middleware.ts
export function middleware(request: NextRequest) {
  const hostname = request.headers.get('host')!;
  const subdomain = hostname.split('.')[0];
  
  // Fetch organization by subdomain
  // Set in request context
}

MVP 機能の優先順位付け

第 1~2 週:基盤
  • プロジェクトのセットアップ(Next.js + Supabase)
  • データベーススキーマの作成
  • 認証(メール + OAuth)
  • 基本的なレイアウトとルーティング
  • ランディングページ
第 3~4 週:コア機能
  • ダッシュボードのレイアウト
  • 最初のコア機能(主な価値提案)
  • ユーザープロフィールの管理
  • 組織/チームの作成
  • チームメンバーの招待
第 5~6 週:収益化
  • 料金ページ
  • Stripe の統合
  • サブスクリプション管理
  • 利用制限の適用
  • 請求ページ
第 7~8 週:仕上げ
  • メール通知
  • エラー処理
  • ローディング状態
  • レスポンシブデザイン
  • SEO の最適化
第 9 週:リリース準備
  • テスト(E2E、ユニット)
  • セキュリティ監査
  • パフォーマンスの最適化
  • ドキュメント
  • デプロイスクリプト

環境変数

# .env.local

# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://xxxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-key

# Stripe
STRIPE_SECRET_KEY=sk_test_xxxxx
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_xxxxx
STRIPE_WEBHOOK_SECRET=whsec_xxxxx

# App
NEXT_PUBLIC_APP_URL=http://localhost:3000

# Optional
SENTRY_DSN=your-sentry-dsn
POSTHOG_KEY=your-posthog-key

デプロイチェックリスト

リリース前
  • 環境変数の設定
  • データベースのマイグレーションの実行
  • RLS ポリシーのテスト
  • Stripe ウェブフックの設定
  • ドメインの設定
  • SSL 証明書の有効化
  • アナリティクスの設定
  • エラートラッキング(Sentry)
  • メールサービスの設定
  • 利用規約とプライバシーポリシーのページ
リリース後
  • エラー率の監視
  • サブスクリプションのフローを確認
  • ウェブフックの配信をテスト
  • データベースのパフォーマンスを監視
  • バックアップの設定
  • ステータスページの作成
  • フィーチャーフラグの実装

ベストプラクティス

パフォーマンス
  • デフォルトで Server Components を使用する
  • 適切なキャッシュ戦略を実装する
  • next/image で画像を最適化する
  • ミューテーションには React Server Actions を使用する
  • 負荷の高いコンポーネントは遅延読み込みする
セキュリティ
  • 常に RLS ポリシーを使用する
  • サーバー側で入力を検証する
  • サービスロールキーをクライアントに公開しない
  • プリペアドステートメントを使用する(Supabase が処理します)
  • 機密性の高いエンドポイントにレート制限を実装する
開発
  • TypeScript を厳格に使用する
  • 重要な処理経路のテストを作成する
  • データベースのマイグレーションを使用する(手動で変更しない)
  • API のバージョンを管理する
  • 複雑なロジックを文書化する
ユーザーエクスペリエンス
  • あらゆる場所にローディング状態を追加する
  • 楽観的更新を実装する
  • わかりやすいエラーメッセージを表示する
  • スムーズなオンボーディングを実現する
  • 役立つ空状態を追加する

よくあるパターンのリファレンス

詳しい実装パターンについては、以下のリファレンスドキュメントを参照してください。

  • AUTHENTICATION_PATTERNS.md - OAuth、マジックリンク、MFA
  • BILLING_PATTERNS.md - Stripe の統合、ウェブフック、従量課金
  • MULTI_TENANT_PATTERNS.md - 組織構造、データ分離
  • DEPLOYMENT_STRATEGIES.md - CI/CD、環境、監視

出力形式の例

SaaS を計画する際は、以下を提示してください。

  1. エグゼクティブサマリー

    • 製品の概要
    • 対象ユーザー
    • 主な機能
  2. 技術アーキテクチャ

    • システム図
    • 技術スタックを選定した理由
    • データベーススキーマ
  3. ファイル構成

    • フォルダー構成の全体像
    • 主要ファイルの説明
  4. 開発ロードマップ

    • 週ごとの内訳
    • 機能の優先順位付け
    • 見積もり工数
  5. デプロイ計画

    • インフラストラクチャの設定
    • 環境設定
    • リリースチェックリスト
  6. 次のステップ

    • すぐに行う作業
    • セットアップコマンド
    • ドキュメントへのリンク

成功基準

適切に計画された SaaS には、以下が必要です。

  • ✅ RLS を含む明確なデータベーススキーマ
  • ✅ 安全な認証フロー
  • ✅ スケーラブルなアーキテクチャ
  • ✅ 動作する課金統合
  • ✅ 整理されたファイル構成
  • ✅ 現実的なスケジュール
  • ✅ 明確な MVP の範囲
  • ✅ デプロイ戦略

コメント