ブログ一覧に戻る
💻Dev Studio

Next.js 14で作る!月100万円稼ぐSaaSの作り方【完全ガイド】

Next.js 14 App RouterとStripeを使った収益性の高いSaaS開発の実践ガイド。実際に月100万円を達成したアーキテクチャを公開。

7 min read
Next.jsReactSaaSStripe副業TypeScript
シェア:

たった一人で月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つの鍵

  1. 技術選定を間違えない

    • Next.js 14の最新機能を活用
    • 実績のあるライブラリを選択
  2. 収益モデルを最初から組み込む

    • 無料プランで価値を証明
    • 段階的な価格設定
  3. 継続的な改善

    • ユーザーフィードバックを即反映
    • A/Bテストで最適化

SaaS開発は、正しい技術と戦略があれば、個人でも十分勝負できる市場です。

この記事のコードをベースに、あなたも月100万円SaaSに挑戦してみませんか?


次回予告: 「GitHub Copilotを超えた!Cursor + AIで開発速度10倍の世界」

実装で詰まったら、コメント欄で質問してください。一緒に解決しましょう!

ゆうき|毎月20万円積立のプロフィール画像

ゆうき|毎月20万円積立

メガベンチャー シニアエンジニア

Flutter、Next.js、AIを活用した開発を専門とするエンジニア。29歳で資産1000万円を運用中。テクノロジーと投資を組み合わせて、45歳でのサイドFIRE達成を目指しています。

7年以上の開発経験
専門分野:
FlutterNext.jsAI/Claudeシステム設計投資戦略
資格・認定:
  • 年収850万円(29歳)
  • VOO・BND中心に1000万円運用
検証済み専門家