ブログ一覧に戻る💻Dev Studio
Next.js 14で作る!月100万円稼ぐSaaSの作り方【完全ガイド】
Next.js 14 App RouterとStripeを使った収益性の高いSaaS開発の実践ガイド。実際に月100万円を達成したアーキテクチャを公開。
7 min read
Next.jsReactSaaSStripe副業TypeScript
シェア:
目次0% 完了
たった一人で月100万円のSaaSを作る。
夢物語に聞こえるかもしれませんが、Next.js 14とモダンな技術スタックを使えば、これは十分に実現可能な目標です。
私が実際に開発・運営している複数のSaaSプロダクトの経験から、再現性の高い開発手法を完全公開します。
なぜNext.js 14なのか?
圧倒的な開発速度
// App Router + Server Componentsの威力
// APIルート不要でデータフェッチ
async function Dashboard() {
const user = await getCurrentUser()
const subscription = await getSubscription(user.id)
const usage = await getUsageStats(user.id)
return (
<DashboardLayout>
<StatsCard data={usage} />
<SubscriptionStatus plan={subscription} />
</DashboardLayout>
)
}
従来のSPA開発と比べて、開発速度が3倍向上しました。
収益化の肝:料金プランの設計
実際に使っているプラン構成
// 料金プラン設定(月額)
const PRICING_PLANS = {
free: {
name: 'Free',
price: 0,
features: {
apiCalls: 100,
storage: '100MB',
support: 'Community',
},
},
starter: {
name: 'Starter',
price: 2980, // ¥2,980
features: {
apiCalls: 10000,
storage: '10GB',
support: 'Email',
},
},
pro: {
name: 'Pro',
price: 9980, // ¥9,980
features: {
apiCalls: 100000,
storage: '100GB',
support: 'Priority',
customDomain: true,
},
},
enterprise: {
name: 'Enterprise',
price: 49800, // ¥49,800
features: {
apiCalls: 'Unlimited',
storage: 'Unlimited',
support: 'Dedicated',
customDomain: true,
sla: '99.9%',
},
},
};
ポイント:
- Free プランで価値を体験
- Starter で個人ユーザー獲得
- Pro で本気のユーザーを収益化
- Enterprise で大口顧客を確保
技術スタック完全解説
フロントエンド
{
"dependencies": {
"next": "14.1.0",
"react": "^18.2.0",
"@tanstack/react-query": "^5.0.0",
"zustand": "^4.4.0",
"zod": "^3.22.0",
"tailwindcss": "^3.4.0",
"@radix-ui/react-dialog": "^1.0.0",
"framer-motion": "^10.0.0"
}
}
バックエンド構成
// app/api/v1/route.ts
import { NextRequest } from 'next/server';
import { z } from 'zod';
import { rateLimit } from '@/lib/rate-limit';
import { authenticate } from '@/lib/auth';
const requestSchema = z.object({
action: z.enum(['create', 'update', 'delete']),
data: z.record(z.any()),
});
export async function POST(request: NextRequest) {
// レート制限
const limiter = await rateLimit(request);
if (!limiter.success) {
return new Response('Too Many Requests', { status: 429 });
}
// 認証
const session = await authenticate(request);
if (!session) {
return new Response('Unauthorized', { status: 401 });
}
// バリデーション
const body = await request.json();
const validated = requestSchema.parse(body);
// ビジネスロジック
const result = await processRequest(validated, session);
return Response.json(result);
}
Stripe決済の実装
1. Stripe Checkoutの設定
// app/api/checkout/route.ts
import Stripe from 'stripe';
import { currentUser } from '@/lib/auth';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function POST(req: Request) {
const user = await currentUser();
const { priceId } = await req.json();
const session = await stripe.checkout.sessions.create({
customer_email: user.email,
payment_method_types: ['card'],
line_items: [
{
price: priceId,
quantity: 1,
},
],
mode: 'subscription',
success_url: `${process.env.NEXT_PUBLIC_URL}/dashboard?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.NEXT_PUBLIC_URL}/pricing`,
metadata: {
userId: user.id,
},
});
return Response.json({ url: session.url });
}
2. Webhookでサブスクリプション管理
// app/api/webhook/stripe/route.ts
export async function POST(req: Request) {
const signature = req.headers.get('stripe-signature')!;
const body = await req.text();
const event = stripe.webhooks.constructEvent(
body,
signature,
process.env.STRIPE_WEBHOOK_SECRET!
);
switch (event.type) {
case 'checkout.session.completed':
await handleCheckoutComplete(event.data.object);
break;
case 'customer.subscription.updated':
await handleSubscriptionUpdate(event.data.object);
break;
case 'customer.subscription.deleted':
await handleSubscriptionCancel(event.data.object);
break;
}
return new Response(null, { status: 200 });
}
パフォーマンス最適化
1. Suspenseとストリーミング
// app/dashboard/page.tsx
import { Suspense } from 'react';
export default function DashboardPage() {
return (
<div className="grid grid-cols-3 gap-6">
<Suspense fallback={<StatsCardSkeleton />}>
<RevenueStats />
</Suspense>
<Suspense fallback={<ChartSkeleton />}>
<UsageChart />
</Suspense>
<Suspense fallback={<TableSkeleton />}>
<RecentTransactions />
</Suspense>
</div>
);
}
2. 画像最適化
import Image from 'next/image';
export function ProductImage({ src, alt }) {
return (
<Image
src={src}
alt={alt}
width={800}
height={600}
placeholder="blur"
blurDataURL={generateBlurDataURL()}
loading="lazy"
className="rounded-lg shadow-xl"
/>
);
}
データベース設計(Prisma + PostgreSQL)
// prisma/schema.prisma
model User {
id String @id @default(cuid())
email String @unique
name String?
subscription Subscription?
usage Usage[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Subscription {
id String @id @default(cuid())
userId String @unique
user User @relation(fields: [userId], references: [id])
stripeCustomerId String @unique
stripePriceId String
stripeStatus String
currentPeriodEnd DateTime
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Usage {
id String @id @default(cuid())
userId String
user User @relation(fields: [userId], references: [id])
apiCalls Int @default(0)
storage BigInt @default(0)
period DateTime @default(now())
@@index([userId, period])
}
セキュリティ対策
1. API認証とレート制限
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { verifyToken } from '@/lib/auth';
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(100, '1 m'),
});
export async function middleware(request: NextRequest) {
// API認証
if (request.nextUrl.pathname.startsWith('/api')) {
const token = request.headers.get('authorization')?.replace('Bearer ', '');
if (!token || !(await verifyToken(token))) {
return new NextResponse('Unauthorized', { status: 401 });
}
// レート制限
const ip = request.ip ?? '127.0.0.1';
const { success } = await ratelimit.limit(ip);
if (!success) {
return new NextResponse('Too Many Requests', { status: 429 });
}
}
return NextResponse.next();
}
運用とスケーリング
デプロイ(Vercel)
// vercel.json
{
"functions": {
"app/api/heavy-task/route.ts": {
"maxDuration": 60
}
},
"crons": [
{
"path": "/api/cron/daily-report",
"schedule": "0 9 * * *"
}
]
}
モニタリング設定
// lib/monitoring.ts
import * as Sentry from '@sentry/nextjs';
import { Analytics } from '@vercel/analytics/react';
export function initMonitoring() {
Sentry.init({
dsn: process.env.SENTRY_DSN,
tracesSampleRate: 0.1,
environment: process.env.NODE_ENV,
});
}
export function trackEvent(name: string, properties?: Record<string, any>) {
// Mixpanel, Amplitude, PostHog etc.
analytics.track(name, properties);
}
収益化のロードマップ
Month 1-2: MVP開発
- 核となる機能の実装
- 基本的なUI/UX
- Stripe決済統合
Month 3-4: ベータローンチ
- Product Huntでローンチ
- 初期ユーザー獲得(目標: 100人)
- フィードバック収集と改善
Month 5-6: 本格展開
- SEO対策とコンテンツマーケティング
- 有料広告(Google Ads, Facebook)
- アフィリエイトプログラム開始
Month 7-12: スケール
- 機能拡張
- 価格最適化
- チーム拡大検討
実際の収益推移
Month 1: ¥0
Month 2: ¥15,000(5ユーザー)
Month 3: ¥89,400(30ユーザー)
Month 4: ¥238,600(80ユーザー)
Month 5: ¥447,100(150ユーザー)
Month 6: ¥745,200(250ユーザー)
Month 7: ¥1,046,400(350ユーザー)
まとめ:成功の3つの鍵
-
技術選定を間違えない
- Next.js 14の最新機能を活用
- 実績のあるライブラリを選択
-
収益モデルを最初から組み込む
- 無料プランで価値を証明
- 段階的な価格設定
-
継続的な改善
- ユーザーフィードバックを即反映
- A/Bテストで最適化
SaaS開発は、正しい技術と戦略があれば、個人でも十分勝負できる市場です。
この記事のコードをベースに、あなたも月100万円SaaSに挑戦してみませんか?
次回予告: 「GitHub Copilotを超えた!Cursor + AIで開発速度10倍の世界」
実装で詰まったら、コメント欄で質問してください。一緒に解決しましょう!
