セットアップガイド
StartPack - 30分で始めるSaaS開発
# 概要
StartPackは、TypeScript + Next.jsをベースに、認証、決済、お問い合わせフォームが事前設定された完全なSaaS基盤を提供します。
主要機能
- ユーザー認証(Neon Auth または Supabase Authから選択)
- Stripeによるサブスクリプション決済
- Resendによるお問い合わせメール送信
- PostgreSQL(Neon / Supabase から選択、Prisma ORM使用)
- Tailwind CSS + shadcn/uiコンポーネント
ディレクトリ構造
startpack/
├── app/ # Next.js App Router
│ ├── api/ # APIルート
│ ├── auth/ # 認証ページ
│ ├── billing/ # サブスクリプション管理
│ ├── dashboard/ # ユーザーダッシュボード
│ ├── contact/ # お問い合わせフォーム
│ ├── handler/ # Stack Auth設定
│ └── legal/ # 利用規約等
├── components/ # Reactコンポーネント
├── lib/ # ユーティリティ関数
├── prisma/ # データベーススキーマ
├── config/ # アプリ設定
├── public/ # 静的ファイル
└── styles/ # グローバルスタイル
# チュートリアル動画
StartPackの機能紹介から実際のセットアップ手順まで、動画でわかりやすく解説しています。
# クイックスタート
アプリケーションをローカル環境で起動する手順です。
# unzip
unzip startpack.zip
cd startpack
# 初期化(必ず先に実行)
npm run startpack:init
# プロンプトで番号を入力してプロバイダを選択します
# 1) Neon(StackAuth) / 2) Supabase Auth
# 選択したプロバイダ以外の依存とコードは自動で削除されます
# 依存をインストール(init の後)
npm install
理由: 初期化スクリプトが package.json
の依存関係やファイル構成をプロバイダに合わせて変更するため、先に初期化 → その後インストール の順にしてください。
共通準備
- 環境変数ファイルをコピー(どちらか一方を選択)
# Neon を使う場合 cp .env.neon.example .env # Supabase を使う場合 cp .env.supabase.example .env
- 依存関係をインストール
npm install
- .env を開き、下記「環境変数」を参考に必要項目を埋めます
4. Neonデータベースの接続情報を設定
- neon.tech でプロジェクトを作成(AWSリージョンはシンガポールがおすすめ)
- ダッシュボードの 「Connect to your database」 セクションを見つける
- 「Connect」 ボタンをクリック
- 表示された 「Connection string」 をコピー(パスワード含む)
.env
のDATABASE_URL
に貼り付け
# .env DATABASE_URL="postgresql://user:password@ep-xxx.region.aws.neon.tech/neondb?sslmode=require"
5. データベースを初期化
npx prisma migrate dev
Prisma CLI は .env
を自動読み込みします。DATABASE_URL
は .env
に必ず設定してください。
6. 開発サーバーを起動
npm run dev
認証、決済、メールなどを利用する場合は、下記の環境変数の .env
をすべて埋めてから実行してください。
http://localhost:3002
でアプリケーションにアクセスできます
# 環境変数
プロバイダ別のテンプレートを切替表示できます。
# データベース(Neon)
DATABASE_URL=postgresql://user:password@host/database?sslmode=require
# 認証(Neon Auth / StackAuth)
NEXT_PUBLIC_STACK_PROJECT_ID=your-project-id
NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=your-publishable-key
STACK_SECRET_SERVER_KEY=your-secret-server-key
# 決済(Stripe)
STRIPE_SECRET_KEY=sk_test_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_PRICE_ID=price_...
STRIPE_WEBHOOK_SECRET=whsec_...
# メール(Resend)
RESEND_API_KEY=re_...
CONTACT_EMAIL=you@example.com
# RESEND_FROM=support@yourdomain.com
# RESEND_DOMAIN=yourdomain.com
# レート制限(任意)
CONTACT_RATE_LIMIT_WINDOW_MS=60000
CONTACT_RATE_LIMIT_MAX=5
# 認証設定
StartPack は Neon Auth(StackAuth)または Supabase Auth を選択できます(npm run startpack:init
)。未選択のコードと依存は自動削除されます。
Neon Auth設定
Neon AuthはNeonデータベースに統合された認証システムで、内部でStackAuthの強力な認証機能を利用しています。
セットアップ手順
- Neonダッシュボードで Auth を有効化
- Configuration タブを開き、表示される環境変数をコピーして
.env
に設定 - Settings → Project ID から
NEON_PROJECT_ID
をコピーして.env
に設定 - 右上のアカウントアイコン → Account settings → Personal API keys で API トークンを作成し、
NEON_API_TOKEN
として.env
に設定
必要な環境変数(Configurationからコピー)
変数 | 説明 |
---|---|
NEXT_PUBLIC_STACK_PROJECT_ID | StackAuth プロジェクトID |
NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY | 公開クライアントキー |
STACK_SECRET_SERVER_KEY | サーバーシークレットキー |
NEON_PROJECT_ID | Neonプロジェクト ID(退会機能で必須) |
NEON_API_TOKEN | Neon API トークン(退会機能で必須) |
📖 詳細なセットアップ手順:Neon Auth Next.jsクイックスタート
Neon Auth はプロジェクトごとに1構成
同一の Neon プロジェクト内に複数の Neon Auth 構成は設定できません。開発環境と本番環境で認証を分離したい場合は、 環境ごとに 別々の Neon プロジェクト を作成してください。
推奨手順
- Neon コンソールで 開発用プロジェクト(例: startpack-dev) を作成し、Auth を有効化。Configuration の値を
.env
に設定。 - 別途 本番用プロジェクト(例: startpack-prod) を作成し、同様に Auth を有効化。本番環境の環境変数に設定。
- 各環境で使用する
DATABASE_URL
は、それぞれのプロジェクトの接続文字列を使用します。 - ユーザーや
users_sync
テーブルは環境ごとに独立します(開発と本番で混ざりません)。
既に単一プロジェクトで運用している場合は、もう一つプロジェクトを新規作成し、Auth を有効化後にアプリ側の環境変数(StackAuth と DATABASE_URL)を切り替えてください。
🔧 設定をカスタマイズしたい場合: Neon Auth の設定ページにあるClaim projectからプロジェクトを Stack Auth に移管すると、 Stack Auth ダッシュボード上で詳細な設定や管理が可能になります。
# 決済連携
Stripe設定
開発環境でのテスト: Stripe は必ず テストモード(サンドボックス) を使用してください。 APIキー、商品/価格、Webhook いずれも テストモード で取得・作成したものを使います。
- キーの接頭辞:
sk_test_
/pk_test_
- Price ID はテストモードで作成したもの(例:
price_...
) - Webhook シークレットもテストモード側で取得
1. APIキー
- Stripeダッシュボードにログイン
- (開発環境)右上の テストモード(サンドボックス)をON
- 開発者 → APIキー に移動
- 公開可能キー(
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
)とシークレットキー(STRIPE_SECRET_KEY
)の両方をコピー
.env に設定:
STRIPE_SECRET_KEY=sk_test_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
2. サブスクリプション商品の作成
開発環境では テストモード 側で商品/価格を作成してください(本番とは別管理です)。
- 商品 → 商品を追加 に移動
- 商品名と価格を設定
- 課金タイプは「継続」、請求周期は「毎月」を選択
- Price ID(形式:
price_xxx
)をコピー
.env に設定:
STRIPE_PRICE_ID=price_...
3. Webhook設定
エンドポイントURL:
https://yourdomain.com/api/stripe/webhook
必要なイベント(StartPackのサブスク実装):
checkout.session.completed
(初回決済完了 → サブスクリプション保存)customer.subscription.updated
(キャンセル予約・期間更新 等の反映)customer.subscription.deleted
(サブスクリプション終了)invoice.payment_succeeded
(月額課金の成功を反映)invoice.payment_failed
(課金失敗を反映)
なお、必要に応じて受信するWebhookイベントや処理内容は調整してください(例: トライアル開始/終了、支払い要承認など)。
Stripe CLIでのローカル開発
インストール手順はStripe CLI公式ドキュメントを参照してください。
事前に Stripe ダッシュボードへ テスト(サンドボックス)環境でログインしてから、下記の stripe login
を実行してください(ブラウザが開いて認証されます)。
# ログイン
stripe login
# webhookをlocalhostに転送
stripe listen --forward-to localhost:3002/api/stripe/webhook
# 表示されたwebhookシークレットをコピー
.env に設定:
STRIPE_WEBHOOK_SECRET=whsec_...
テストカード:Stripeテストカード一覧
# 有料プラン加入中の判定
推奨ロジック
- プラン加入中(有効扱い): active / trialing / past_due
- 加入なし(利用不可扱い): canceled / incomplete / unpaid
- キャンセル予約中: cancelAtPeriodEnd === true(期日までは利用可能)
クライアント側(React)の例
// 任意のコンポーネント内
import { useEffect, useState } from 'react'
type SubStatus = 'active' | 'trialing' | 'past_due' | 'canceled' | 'incomplete' | 'unpaid'
function usePaidPlan() {
const [loading, setLoading] = useState(true)
const [isPaid, setIsPaid] = useState(false)
const [cancelAtPeriodEnd, setCancelAtPeriodEnd] = useState(false)
useEffect(() => {
let mounted = true
;(async () => {
try {
const res = await fetch('/api/stripe/subscription', { cache: 'no-store' })
if (!mounted) return
if (!res.ok) { setIsPaid(false); setLoading(false); return }
const data = await res.json()
const status: SubStatus | null = data?.subscription?.status ?? null
const cancelFlag: boolean = Boolean(data?.subscription?.cancelAtPeriodEnd)
const paid = status === 'active' || status === 'trialing' || status === 'past_due'
setIsPaid(Boolean(status && paid))
setCancelAtPeriodEnd(cancelFlag)
} catch { setIsPaid(false) } finally { setLoading(false) }
})()
return () => { mounted = false }
}, [])
return { loading, isPaid, cancelAtPeriodEnd }
}
// 使い方例
export default function PremiumGate({ children }: { children: React.ReactNode }) {
const { loading, isPaid, cancelAtPeriodEnd } = usePaidPlan()
if (loading) return <div className='text-gray-400 text-sm'>読み込み中...</div>
if (!isPaid) return <div className='text-gray-400 text-sm'>プラン登録が必要です</div>
return <div>{children}{cancelAtPeriodEnd && <div className='mt-2 text-xs text-gray-500'>キャンセル予定(期日まではご利用可)</div>}</div>
}
サーバー側(Route / Server Component)の例
// 例) /app/premium/page.tsx (Server Component)
import 'server-only'
async function fetchSubscription() {
const res = await fetch('/api/stripe/subscription', { cache: 'no-store' })
if (!res.ok) return null
return res.json()
}
export default async function PremiumPage() {
const data = await fetchSubscription()
const status = data?.subscription?.status as ('active'|'trialing'|'past_due'|'canceled'|'incomplete'|'unpaid') | undefined
const isPaid = status === 'active' || status === 'trialing' || status === 'past_due'
if (!isPaid) {
return <div className='p-6 text-sm text-gray-400'>プラン登録が必要です</div>
}
return <div className='p-6'>有料ページの内容</div>
}
# カスタマーポータル
Stripeのカスタマーポータルを有効化して、ユーザーが自分で支払い方法や請求履歴、キャンセル/再開を管理できるようにします。
Stripeダッシュボードで有効化
- Stripeダッシュボード → 設定 → Billing → カスタマーポータル
- カスタマーポータルのリンクを有効にする をオン
# メール設定
Resendセットアップ
- resend.com でサインアップ
- ダッシュボードでAPIキーを作成
RESEND_API_KEY
に追加- お問い合わせフォームの受信用に
CONTACT_EMAIL
を設定
ドメイン検証(本番環境)
- Resendダッシュボードでドメインを追加
- DNSレコードを設定(SPF、DKIM)
- ドメイン所有権を確認
- .envの
RESEND_DOMAIN
を更新
⚠️ 開発環境の制限: ドメイン検証なしでは、アカウント所有者のメールアドレスにのみメールを送信できます。
さらに、未検証ドメインでは送信元は onboarding@resend.dev のみ許可されます(noreply@resend.dev などは不可)。
ローカルでは CONTACT_EMAIL
を Resend アカウントのメール(または許可された受信先)に設定してください。
Neon(Stack Auth)の認証メールをカスタマイズするには:
Neon コンソール → Auth → Project ownership → Claim project を実行すると、 Stack Auth 側でメールテンプレート(確認メール、パスワードリセット等)を編集できます。
# お問い合わせフォームのレートリミット
お問い合わせフォームには、スパム防止のためのレートリミット機能が搭載されています。 同一IPアドレスから短時間に大量のリクエストが送信された場合、一時的にアクセスを制限します。
デフォルト設定
- 制限時間: 60秒間
- 最大送信回数: 5回
- 制限対象: IPアドレス単位
つまり、同じIPアドレスから60秒間に6回目以降のお問い合わせは一時的に拒否されます。
設定のカスタマイズ
環境変数で制限を調整できます:
CONTACT_RATE_LIMIT_WINDOW_MS
: 制限時間(ミリ秒)。デフォルト60000
(60秒)CONTACT_RATE_LIMIT_MAX
: 最大送信回数。デフォルト5
制限時の表示: 制限に達すると「リクエストが多すぎます。しばらく時間をおいて再度お試しください。」というメッセージが表示されます。
# お問い合わせメールのカスタマイズ
お問い合わせフォームのメール送信は /app/api/contact/route.ts
で実装されています。 テキスト形式のメールで、件名・本文・送信元を自由にカスタマイズできます。
現在の設定
- メール形式: テキスト形式
- 送信元:
Contact Form <onboarding@resend.dev>
- 送信先:
CONTACT_EMAIL
環境変数 - 返信先: ユーザーが入力したメールアドレス
- 件名:
[お問い合わせ] ○○様からのお問い合わせ
カスタマイズ例
コード内の以下の部分を編集してください:
1. 件名の変更
subject: `【サービス名】お問い合わせ from ${safeName}`
2. 送信元の変更(ドメイン検証後)
from: `サポート <support@yourdomain.com>`
3. 本文テンプレートの変更
text: `お問い合わせありがとうございます
お名前: ${safeName}
メールアドレス: ${safeEmail}
お問い合わせ内容:
${safeMessage}
---
このメールは自動送信されています。
サポートチーム`
開発環境での注意: Resend でドメイン未検証の場合、送信元は onboarding@resend.dev
のみ使用可能です。CONTACT_EMAIL
をResendアカウントのメールに設定してください。
# トラブルシューティング
データベース接続に失敗する
一般的な原因:
- .env に
DATABASE_URL
が未設定 / タイプミス - DATABASE_URLフォーマットが正しくない
?sslmode=require
パラメータがない- データベースが初期化されていない
解決方法:
# Prismaクライアントを生成(初回/スキーマ変更時)
npx prisma generate
# データベースをリセット
npx prisma migrate reset
# 注意: Prismaは .env を読み込みます(.env.local のみではNG)
認証が機能しない
確認事項:
- Neon Auth が有効で、Configuration から環境変数をコピー済み
NEXT_PUBLIC_STACK_PROJECT_ID
/NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY
/STACK_SECRET_SERVER_KEY
が設定されている- ブラウザでCookieが有効になっている
Stripe決済が失敗する
確認事項:
STRIPE_PRICE_ID
が「継続・毎月」のPriceに対応している- 公開可能キー/シークレットキーのモード(テスト/本番)が一致
- Webhookエンドポイントが正しく設定され、必要イベントが選択されている
STRIPE_WEBHOOK_SECRET
が正しい(Invalid signature は取り違えのサイン)- ローカル:
stripe listen
実行中(転送先: localhost:3002)
メールが送信されない
開発環境:
CONTACT_EMAIL
をResendアカウントのメールに設定RESEND_API_KEY
が設定されている- Resendダッシュボードでログを確認
本番環境:
- ドメイン検証を完了
- SPF/DKIMレコードを確認
RESEND_FROM
を本番ドメインの送信元に設定
ビルドエラー
# キャッシュをクリアして再インストール
rm -rf node_modules .next
npm install
npm run build