Skip to content
Published on

スタートアップ技術スタック選択ガイド2025:0→1からシリーズBまでの技術意思決定

Authors

1. 技術(ぎじゅつ)スタック哲学(てつがく)

1.1 Boring Technology原則(げんそく)

Dan McKinleyの「Choose Boring Technology」エッセイは、スタートアップの技術(ぎじゅつ)選択(せんたく)のバイブルです。核心的(かくしんてき)な主張(しゅちょう)はこうです:

イノベーショントークン: すべてのチームには限(かぎ)られた数(かず)の「イノベーショントークン」があります。新(あたら)しく検証(けんしょう)されていない技術(ぎじゅつ)を採用(さいよう)するたびにトークンを消費(しょうひ)します。ビジネス自体(じたい)がすでに1つのイノベーションなので、技術(ぎじゅつ)スタックでまで冒険(ぼうけん)する必要(ひつよう)はありません。

┌─────────────────────────────────────────────────────┐
│         イノベーショントークン配分例                      │
├─────────────────────────────────────────────────────┤
│                                                     │
│  保有イノベーショントークン: 3個                         │
│                                                     │
│  良い例:│  ├── ビジネスモデル (革新) ......... トークン1個使用   │
│  ├── PostgreSQL (退屈な技術) ....... トークン0個      │
│  ├── Next.js (実績あり) ........... トークン0個       │
│  ├── 独自MLパイプライン (革新) ..... トークン1個使用   │
│  └── 残りトークン: 1個(予備)                        │
│                                                     │
│  悪い例:│  ├── ビジネスモデル (革新) ......... トークン1個使用   │
│  ├── CockroachDB (新しい) ......... トークン1個使用   │
│  ├── Bun Runtime (新しい) ......... トークン1個使用   │
│  ├── 独自MLパイプライン (革新) ..... トークン不足!│  └── 残りトークン: 0個(危険)                        │
│                                                     │
└─────────────────────────────────────────────────────┘

1.2 Monolith First

Martin Fowlerが主張(しゅちょう)した「Monolith First」原則(げんそく)。マイクロサービスで始(はじ)めず、モノリスで始(はじ)めて必要(ひつよう)な時(とき)に分離(ぶんり)しましょう。

なぜモノリスが先(さき)なのか:

  • サービス境界(きょうかい)を事前(じぜん)に正(ただ)しく分(わ)けることはほぼ不可能(ふかのう)
  • 開発(かいはつ)速度(そくど)が速(はや)い(サービス間(かん)通信(つうしん)のオーバーヘッドなし)
  • デバッグが容易(ようい)(単一(たんいつ)プロセス)
  • トランザクション管理(かんり)が簡単(かんたん)(分散(ぶんさん)トランザクション不要(ふよう))
  • デプロイがシンプル(1つのアーティファクト)
┌──────────────────────────────────────────────────┐
│       アーキテクチャ進化パス                         │
│                                                  │
Stage 0: モノリス (MVP)│  ├── 単一のNext.jsアプリ│  ├── 単一のDB (Supabase)│  └── 単一のデプロイ (Vercel)│           │                                       │
 (PMF達成)Stage 1: モジュラーモノリス                        │
│  ├── ドメイン別モジュール分離                       │
│  ├── 内部API境界の定義                             │
│  └── DBスキーマ分離開始                             │
│           │                                       │
 (チーム10+, トラフィック急増)Stage 2: 選択的サービス分離                        │
│  ├── ボトルネックサービスのみ分離                    │
│  ├── イベント駆動の非同期処理導入                    │
│  └── キャッシュレイヤー追加                         │
│           │                                       │
 (チーム30+, グローバル展開)Stage 3: MSA (必要な場合のみ)│  ├── 独立デプロイ可能なサービス群                    │
│  ├── サービスメッシュ                               │
│  └── 集中型オブザーバビリティ                       │
└──────────────────────────────────────────────────┘

1.3 PaaS-First戦略(せんりゃく)

インフラ管理(かんり)に時間(じかん)を使(つか)わないでください。PaaS(Platform as a Service)で始(はじ)めて、必要(ひつよう)な時(とき)にIaaSに移行(いこう)しましょう。

アプローチインフラ管理時間柔軟性コスト(初期)コスト(成長期)
PaaS (Vercel, Railway)最小無料〜50ドル高くなる可能性
Managed (AWS ECS, GKE)100〜500ドル
Self-managed (K8s on EC2)200〜1000ドル最適化可能

核心(かくしん)原則(げんそく): 初期(しょき)は開発(かいはつ)速度(そくど)がコスト最適化(さいてきか)より重要(じゅうよう)。PMFを見(み)つける前(まえ)にインフラ最適化(さいてきか)に時間(じかん)を費(つい)やすのは時間(じかん)の無駄(むだ)です。


2. Stage 0: MVP(月額(げつがく)0〜50ドル)

2.1 推奨(すいしょう)スタック

┌───────────────────────────────────────────────┐
MVP技術スタック (2025)├───────────────────────────────────────────────┤
│                                               │
Frontend:  Next.js 15 (App Router)Styling:   Tailwind CSS + shadcn/ui          │
Backend:   Next.js API Routes / Server Actions│
Database:  Supabase (PostgreSQL)Auth:      Supabase Auth / NextAuth.jsStorage:   Supabase Storage / Cloudflare R2Deploy:    VercelAnalytics: PostHog (セルフホスト) / PlausiblePayments:  StripeEmail:     ResendMonitoring: Vercel Analytics + Sentry│                                               │
│  月額合計コスト: 0 ~ 50 USD  (Vercel Free + Supabase Free Tier)│                                               │
└───────────────────────────────────────────────┘

2.2 Next.js + Supabaseプロジェクトセットアップ

# プロジェクト作成
npx create-next-app@latest my-startup --typescript --tailwind --app --src-dir
cd my-startup

# コア依存関係のインストール
npm install @supabase/supabase-js @supabase/ssr
npm install stripe @stripe/stripe-js
npm install resend
npm install zod react-hook-form @hookform/resolvers

# UIライブラリ
npx shadcn@latest init
npx shadcn@latest add button card input form dialog toast

# 開発ツール
npm install -D prettier eslint-config-prettier
npm install -D @types/node

2.3 Supabase設定(せってい)

// src/lib/supabase/server.ts
import { createServerClient } 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: {
        getAll() {
          return cookieStore.getAll()
        },
        setAll(cookiesToSet) {
          try {
            cookiesToSet.forEach(({ name, value, options }) =>
              cookieStore.set(name, value, options)
            )
          } catch {
            // Server Componentではset不可 - 無視
          }
        },
      },
    }
  )
}

// src/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!
  )
}

2.4 認証(にんしょう)の実装(じっそう)

// src/app/auth/login/page.tsx
'use client'

import { createClient } from '@/lib/supabase/client'
import { useState } from 'react'
import { useRouter } from 'next/navigation'

export default function LoginPage() {
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [loading, setLoading] = useState(false)
  const router = useRouter()
  const supabase = createClient()

  const handleLogin = async (e: React.FormEvent) => {
    e.preventDefault()
    setLoading(true)

    const { error } = await supabase.auth.signInWithPassword({
      email,
      password,
    })

    if (error) {
      alert(error.message)
    } else {
      router.push('/dashboard')
      router.refresh()
    }
    setLoading(false)
  }

  const handleGoogleLogin = async () => {
    await supabase.auth.signInWithOAuth({
      provider: 'google',
      options: {
        redirectTo: `${window.location.origin}/auth/callback`,
      },
    })
  }

  return (
    <div className="flex min-h-screen items-center justify-center">
      <form onSubmit={handleLogin} className="w-full max-w-md space-y-4 p-8">
        <h1 className="text-2xl font-bold">ログイン</h1>
        <input
          type="email"
          placeholder="メールアドレス"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          className="w-full rounded border p-2"
          required
        />
        <input
          type="password"
          placeholder="パスワード"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          className="w-full rounded border p-2"
          required
        />
        <button
          type="submit"
          disabled={loading}
          className="w-full rounded bg-blue-600 p-2 text-white"
        >
          {loading ? 'ログイン中...' : 'ログイン'}
        </button>
        <button
          type="button"
          onClick={handleGoogleLogin}
          className="w-full rounded border p-2"
        >
          Googleでログイン
        </button>
      </form>
    </div>
  )
}

2.5 Stripe決済(けっさい)連携(れんけい)

// src/app/api/stripe/checkout/route.ts
import { NextRequest, NextResponse } from 'next/server'
import Stripe from 'stripe'
import { createClient } from '@/lib/supabase/server'

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

export async function POST(req: NextRequest) {
  const supabase = await createClient()
  const { data: { user } } = await supabase.auth.getUser()

  if (!user) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
  }

  const { priceId } = await req.json()

  const session = await stripe.checkout.sessions.create({
    customer_email: user.email,
    line_items: [{ price: priceId, quantity: 1 }],
    mode: 'subscription',
    success_url: `${req.nextUrl.origin}/dashboard?success=true`,
    cancel_url: `${req.nextUrl.origin}/pricing?canceled=true`,
    metadata: {
      userId: user.id,
    },
  })

  return NextResponse.json({ url: session.url })
}

2.6 MVPコスト分析(ぶんせき)

サービス無料ティア有料切替時点コスト
Vercel100GB帯域幅、無制限デプロイトラフィック増加時Pro: 20ドル/月
Supabase500MB DB、1GBストレージ使用量増加時Pro: 25ドル/月
Stripe使用量ベース売上発生時2.9% + 30セント/件
Resend100通/日コンバージョンテスト時20ドル/月
Sentry5Kイベント/月エラー増加時26ドル/月
PostHog1Mイベント/月分析高度化時0ドル(セルフホスト)

MVP合計(ごうけい)コスト: 月額(げつがく)0〜50ドル(有料(ゆうりょう)切替(きりかえ)前(まえ)までほぼ無料(むりょう))


3. Stage 1: Product-Market Fit(月額(げつがく)200〜500ドル)

3.1 PMF段階(だんかい)の技術(ぎじゅつ)変化(へんか)

PMFを見(み)つけた後(あと)、以下(いか)の領域(りょういき)でアップグレードが必要(ひつよう)になります:

┌───────────────────────────────────────────────┐
Stage 1 技術スタックアップグレード            │
├───────────────────────────────────────────────┤
│                                               │
Database:   Supabase Pro → より大きなインスタンス│
Cache:      + Redis (Upstash)Queue:      + BullMQ (Redisベース)Search:     + Meilisearch / TypesenseCDN:        + CloudflareMonitoring: + Better Stack / Grafana CloudCI/CD:      GitHub Actions高度化Error:      Sentry Pro│                                               │
│  月額コスト: 200 ~ 500 USD└───────────────────────────────────────────────┘

3.2 Redisキャッシュの追加(ついか)

// src/lib/cache.ts
import { Redis } from '@upstash/redis'

const redis = new Redis({
  url: process.env.UPSTASH_REDIS_REST_URL!,
  token: process.env.UPSTASH_REDIS_REST_TOKEN!,
})

export async function getCached<T>(
  key: string,
  fetcher: () => Promise<T>,
  ttl: number = 3600 // 1時間
): Promise<T> {
  const cached = await redis.get<T>(key)
  if (cached !== null) {
    return cached
  }

  const data = await fetcher()
  await redis.set(key, data, { ex: ttl })

  return data
}

export async function invalidateCache(pattern: string) {
  const keys = await redis.keys(pattern)
  if (keys.length > 0) {
    await redis.del(...keys)
  }
}

3.3 データベース最適化(さいてきか)

-- インデックス最適化
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);
CREATE INDEX CONCURRENTLY idx_orders_user_created
  ON orders(user_id, created_at DESC);
CREATE INDEX CONCURRENTLY idx_products_category_status
  ON products(category_id, status) WHERE status = 'active';

-- パーティショニング(イベントテーブルが大きくなった時)
CREATE TABLE events (
  id BIGSERIAL,
  user_id UUID NOT NULL,
  event_type TEXT NOT NULL,
  payload JSONB,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
) PARTITION BY RANGE (created_at);

CREATE TABLE events_2025_01 PARTITION OF events
  FOR VALUES FROM ('2025-01-01') TO ('2025-02-01');
CREATE TABLE events_2025_02 PARTITION OF events
  FOR VALUES FROM ('2025-02-01') TO ('2025-03-01');

4. Stage 2: Growth(月額(げつがく)2K〜10Kドル)

4.1 成長(せいちょう)段階(だんかい)の技術(ぎじゅつ)スタック

┌───────────────────────────────────────────────┐
Stage 2 技術スタック                     │
├───────────────────────────────────────────────┤
│                                               │
Compute:     AWS ECS Fargate / GKEDatabase:    RDS PostgreSQL Multi-AZCache:       ElastiCache Redis ClusterCDN:         CloudFront + S3Queue:       SQS / RabbitMQSearch:      ElasticSearch / OpenSearchCI/CD:       GitHub Actions + ArgoCDMonitoring:  Datadog / Grafana StackLog:         CloudWatch / LokiIaC:         Terraform│                                               │
│  チーム規模: 1030名                            │
│  月額コスト: 2,000 ~ 10,000 USD└───────────────────────────────────────────────┘

4.2 Kubernetesの導入(どうにゅう)

# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api-server
  template:
    metadata:
      labels:
        app: api-server
    spec:
      containers:
      - name: api
        image: my-registry/api-server:latest
        ports:
        - containerPort: 3000
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        readinessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 10
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 15
          periodSeconds: 20
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api-server-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api-server
  minReplicas: 3
  maxReplicas: 20
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70

4.3 Terraformインフラコード

# terraform/main.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

resource "aws_db_instance" "main" {
  identifier     = "my-startup-production"
  engine         = "postgres"
  engine_version = "16.1"
  instance_class = "db.r6g.large"

  allocated_storage     = 100
  max_allocated_storage = 500
  storage_encrypted     = true

  multi_az               = true
  backup_retention_period = 14

  performance_insights_enabled = true
  monitoring_interval          = 60
}

resource "aws_elasticache_replication_group" "main" {
  replication_group_id       = "my-startup-cache"
  description                = "Redis cache cluster"
  node_type                  = "cache.r6g.large"
  num_cache_clusters         = 2
  automatic_failover_enabled = true
  multi_az_enabled           = true
  engine_version             = "7.0"
  port                       = 6379

  at_rest_encryption_enabled = true
  transit_encryption_enabled = true
}

5. Stage 3: Scale(月額(げつがく)10Kドル以上(いじょう))

5.1 スケール段階(だんかい)アーキテクチャ

┌─────────────────────────────────────────────────────────┐
Stage 3 アーキテクチャ                      │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  ┌──────────┐    ┌──────────┐    ┌──────────┐          │
│  │CloudFront│───>ALB/NLB  │───>K8s      │          │
│  │  + WAF   │    │          │    │ Cluster  │          │
│  └──────────┘    └──────────┘    └──────────┘          │
│                                       │                 │
│                     ┌─────────────────┼──────────┐     │
│                     ▼                 ▼          ▼     │
│              ┌───────────┐   ┌───────────┐ ┌────────┐ │
│              │API Gateway│Worker    │ │ Cron   │ │
│              │  Service  │   │  Service   │ │Service │ │
│              └─────┬─────┘   └─────┬─────┘ └───┬────┘ │
│                    │               │            │      │
│      ┌─────────────┼───────────────┼────────────┤     │
│      ▼             ▼               ▼            ▼     │
│  ┌────────┐  ┌─────────┐   ┌──────────┐  ┌────────┐ │
│  │RDS     │  │Redis    │   │  Kafka   │  │  S3    │ │
│  │Multi-AZ│  │Cluster  │   │ Cluster  │  │        │ │
│  └────────┘  └─────────┘   └──────────┘  └────────┘ │
│                                    │                  │
│                              ┌─────┴─────┐           │
│                              │ElasticSearch│           │
│                              │  Cluster   │           │
│                              └───────────┘           │
│                                                       │
│  チーム規模: 30名以上                                   │
│  月額コスト: 10,000 USD以上                              │
└─────────────────────────────────────────────────────────┘

5.2 イベント駆動(くどう)アーキテクチャ(Kafka)

// src/events/producer.ts
import { Kafka, Partitioners } from 'kafkajs'

const kafka = new Kafka({
  clientId: 'my-startup',
  brokers: (process.env.KAFKA_BROKERS || '').split(','),
  ssl: true,
  sasl: {
    mechanism: 'scram-sha-256',
    username: process.env.KAFKA_USERNAME!,
    password: process.env.KAFKA_PASSWORD!,
  },
})

const producer = kafka.producer({
  createPartitioner: Partitioners.DefaultPartitioner,
  idempotent: true,
})

export async function publishEvent(topic: string, event: {
  type: string
  payload: Record<string, unknown>
}) {
  await producer.connect()

  const message = {
    key: event.payload.id as string || crypto.randomUUID(),
    value: JSON.stringify({
      ...event,
      timestamp: new Date().toISOString(),
      version: '1.0',
    }),
    headers: {
      'event-type': event.type,
      'content-type': 'application/json',
    },
  }

  await producer.send({ topic, messages: [message] })
}

6. フロントエンドスタック

6.1 フレームワーク比較(ひかく)

項目Next.js 15RemixSvelteKit
レンダリングSSR/SSG/ISR/RSCSSR/CSRSSR/SSG/CSR
サーバーコンポーネントあり (RSC)なしなし (rune使用)
ルーティングファイルベース (App Router)ファイルベースファイルベース
データフェッチServer Components, fetchLoader/ActionLoad関数
デプロイVercel最適化、どこでもどこでもどこでも
エコシステム非常に大きい成長中
学習曲線中〜高低〜中
スタートアップ推奨強く推奨推奨小規模チーム推奨

スタートアップの結論(けつろん): 2025年(ねん)時点(じてん)でNext.jsが最(もっと)も安全(あんぜん)な選択(せんたく)。エコシステム、採用(さいよう)市場(しじょう)、デプロイインフラすべての面(めん)で優位(ゆうい)。

6.2 状態(じょうたい)管理(かんり)

ライブラリ複雑度用途スタートアップ推奨
React useState/useContext最小ローカル状態デフォルト
Zustandグローバル状態強く推奨
Jotaiアトミック状態推奨
TanStack Queryサーバー状態必須
Redux Toolkit複雑な状態非推奨(初期)
// Zustandの例 - シンプルなストア
import { create } from 'zustand'
import { persist } from 'zustand/middleware'

interface CartStore {
  items: Array<{ id: string; name: string; price: number; quantity: number }>
  addItem: (item: { id: string; name: string; price: number }) => void
  removeItem: (id: string) => void
  clearCart: () => void
  total: () => number
}

export const useCartStore = create<CartStore>()(
  persist(
    (set, get) => ({
      items: [],
      addItem: (item) =>
        set((state) => {
          const existing = state.items.find((i) => i.id === item.id)
          if (existing) {
            return {
              items: state.items.map((i) =>
                i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i
              ),
            }
          }
          return { items: [...state.items, { ...item, quantity: 1 }] }
        }),
      removeItem: (id) =>
        set((state) => ({
          items: state.items.filter((i) => i.id !== id),
        })),
      clearCart: () => set({ items: [] }),
      total: () =>
        get().items.reduce((sum, item) => sum + item.price * item.quantity, 0),
    }),
    { name: 'cart-storage' }
  )
)

7. バックエンドスタック

7.1 言語(げんご)比較(ひかく)(スタートアップ視点(してん))

項目Node.js (TS)GoPythonJava/Kotlin
開発速度速い速い遅い
性能非常に高い低い高い
採用難易度容易容易
フルスタック可能はい (Next.js)いいえ限定的いいえ
エコシステム非常に大きい大きい非常に大きい (ML)非常に大きい
スタートアップ推奨最高性能重視時ML/データ時エンタープライズ

7.2 スタートアップタイプ別(べつ)推奨(すいしょう)

B2C SaaSNode.js (TypeScript) + Next.js
├── フルスタック1名で全てを構築可能
├── Vercelデプロイでインフラの心配を最小化
└── フロントエンド・バックエンドの型共有

B2B SaaSNode.js or Go
├── APIパフォーマンスが重要ならGo
├── 迅速な機能開発が重要ならNode.js
└── どちらも良い選択

AI/MLスタートアップ → Python + TypeScript
├── MLパイプラインはPython
├── APIサーバーはFastAPI (Python) またはNext.js (TS)
└── フロントエンドはNext.js

フィンテック → Go or Java/Kotlin
├── 高い性能と安定性が必要
├── 静的型システムの安全性
└── 金融規制対応に強いエコシステム

8. データベース

8.1 PostgreSQL vs MySQL

項目PostgreSQLMySQL
JSONサポートJSONB(インデックス可能)JSON(制限的)
全文検索内蔵(tsvector)内蔵(FULLTEXT)
拡張性豊富(PostGIS等)制限的
レプリケーション論理的/物理的物理的(デフォルト)
性能読み書きバランス読み取り最適化
スタートアップ推奨強く推奨MySQL経験チームならOK

8.2 BaaS比較(ひかく)

サービスDBエンジン開始価格特徴スタートアップ推奨
SupabasePostgreSQL無料Auth、Storage、Realtime含むMVP最高
NeonPostgreSQL無料サーバーレス、ブランチング良い
PlanetScaleMySQL (Vitess)無料ブランチング、無停止スキーマ変更良い
TursoSQLite (libSQL)無料エッジ、非常に高速小規模アプリ
CockroachDBPostgreSQL互換有料分散SQL、グローバル大規模アプリ

8.3 Redis導入(どうにゅう)のタイミング

Redis導入が必要な時:
├── 同じクエリが毎秒100回以上実行される時
├── APIレスポンス時間が500msを超える時
├── セッション管理が必要な時
├── リアルタイム機能(リーダーボード、カウンター等)が必要な時
├── レートリミットが必要な時
└── 分散ロックが必要な時

Redisがまだ不要な時:
├── DAU 1,000以下
├── DBクエリが20ms以内
├── Next.jsの組み込みキャッシュで十分な時
└── コストが制約になる時

9. インフラストラクチャ

9.1 PaaSからIaaSへの移行(いこう)タイミング

Vercel/RailwayからAWS/GCPへ移行すべき時:

1. コスト閾値
   ├── Vercel Proのコストが月500ドルを超える時
   ├── 帯域幅コストがコンピューティングより高い時
   └── 関数実行時間の制限に引っかかる時

2. 技術的要件
   ├── ロングランニングプロセスが必要な時(5分以上)
   ├── WebSocketがコア機能の時
   ├── GPUコンピューティングが必要な時
   └── カスタムネットワーク構成が必要な時

3. 規制/コンプライアンス
   ├── データ保存場所の規制
   ├── SOC 2HIPAA等の認証が必要な時
   └── VPC隔離が必要な時

移行不要な場合:
├── 月額コスト500ドル以下
├── サーバーレスアーキテクチャで十分
├── DevOps専任者がいない
└── トラフィックが予測可能で安定

10. 採用(さいよう)と技術(ぎじゅつ)スタック

10.1 技術(ぎじゅつ)スタックが採用(さいよう)に与(あた)える影響(えいきょう)

技術(ぎじゅつ)スタックの選択(せんたく)は採用(さいよう)プールを直接(ちょくせつ)決定(けってい)します:

技術開発者プールジュニア比率シニア採用難易度
React/Next.js非常に大きい高い
TypeScript大きい
Python非常に大きい高いML分野で困難
Go小さい低い高い
Rust非常に小さい非常に低い非常に高い
Java/Kotlin非常に大きい高い

10.2 採用(さいよう)しやすい技術(ぎじゅつ)スタック

採用が容易なスタック:
├── Next.js + TypeScript(フロント)
├── Node.js + Express/Nest.js(バックエンド)
├── PostgreSQL / MySQL(DB├── Redis(キャッシュ)
├── Docker + GitHub Actions(DevOps)
└── AWS(インフラ)

採用が困難なスタック:
├── Svelte / SolidJS(フロント)
├── Rust / Elixir(バックエンド)
├── CockroachDB / ScyllaDB(DB├── Nomad / Consul(オーケストレーション)
└── Pulumi / CDK(IaC)

11. よくある失敗(しっぱい)

11.1 履歴書(りれきしょ)駆動(くどう)開発(かいはつ)(Resume-Driven Development)

悪い意思決定パターン:
├── "K8sの経験を積みたい"K8s導入 (DAU 100)
├── "Rustがトレンドだから"RustでAPIサーバー (チーム2)
├── "MSAが正統だから"3名で10サービス運用
├── "GraphQLが良いらしい" → シンプルCRUDにGraphQL
└── "Googleがモノレポを使っている"3名チームにTurborepo

正しい意思決定:
├── "この問題を解決する最もシンプルな方法は?"
├── "6ヶ月後もメンテナンスできるか?"
├── "チームメンバーが既に慣れた技術か?"
├── "採用が容易な技術か?"
└── "移行コストは許容範囲か?"

11.2 早期(そうき)最適化(さいてきか)

  • DBインデックス40個(こ)を事前(じぜん)作成(さくせい)(クエリパターンも分(わ)からない状態(じょうたい)で)
  • 3段階(だんかい)のキャッシュレイヤー構築(こうちく)(トラフィック100人(にん)/日(にち))
  • グローバルCDN設定(せってい)(ユーザー全員(ぜんいん)が1つの国(くに))
  • ロードバランサー + Auto Scaling(CPU使用率(しようりつ)5%)

11.3 早期(そうき)マイクロサービス

3名(めい)のチームがマイクロサービスを導入(どうにゅう)すると:

  • サービス間(かん)通信(つうしん)のデバッグに丸一日(まるいちにち)
  • 分散(ぶんさん)トランザクション問題(もんだい)でデータ不整合(ふせいごう)
  • 10個(こ)のデプロイパイプラインの管理(かんり)
  • モニタリングダッシュボードのセットアップだけで2週間(しゅうかん)
  • 結果(けっか):開発(かいはつ)速度(そくど)50%減少(げんしょう)、インフラコスト300%増加(ぞうか)

バランス: MVP段階(だんかい)では80/20ルールを適用(てきよう)。20%のコアコードに80%の品質(ひんしつ)を投資(とうし)しましょう。


12. 実例(じつれい)

12.1 Vercelスタック

Vercel (Next.js開発元):
├── Frontend: Next.js(自社製品)
├── Backend: Go + Node.js
├── Database: PlanetScale (MySQL)Neon (PostgreSQL)
├── Cache: Redis (Upstash)
├── Monorepo: Turborepoベース
├── Deploy: 自社プラットフォーム
├── Monitoring: 自社構築 + Datadog
└── 特徴: 自社製品を積極活用(ドッグフーディング)

12.2 Linearスタック

Linear (プロジェクト管理ツール):
├── Frontend: React + TypeScript
├── Backend: Node.js + TypeScript
├── Database: PostgreSQL
├── Cache: Redis
├── Sync: CRDTベースのリアルタイム同期
├── Deploy: Google Cloud
├── Monitoring: 自社構築
└── 特徴: オフラインファースト、ローカルデータ優先

12.3 Cal.comスタック

Cal.com (オープンソースのスケジュール管理):
├── Frontend: Next.js + TypeScript
├── Backend: tRPC + Prisma
├── Database: PostgreSQL
├── Auth: NextAuth.js
├── Deploy: Vercel
├── Monorepo: Turborepo
└── 特徴: オープンソース、モノレポ、tRPCで型安全

12.4 示唆点(しさてん)

これらの事例(じれい)の共通点(きょうつうてん):

  1. **実績(じっせき)のある技術(ぎじゅつ)**が主流(しゅりゅう)(PostgreSQL、Redis、TypeScript)
  2. モノリスまたはモジュラーモノリスで開始(かいし)
  3. TypeScriptをデフォルトとして採用(さいよう)
  4. PaaS優先(ゆうせん)(Vercel、Google Cloud マネージド)
  5. コア差別化(さべつか)ポイントにのみイノベーション投資(とうし)

13. クイズ

Q1: Boring Technology原則における「イノベーショントークン」とは何で、なぜ節約すべきですか?

A: イノベーショントークンはDan McKinleyが提案(ていあん)した概念(がいねん)で、チームが対処(たいしょ)できる新(あたら)しい/検証(けんしょう)されていない技術(ぎじゅつ)の数(かず)を意味(いみ)します。すべてのチームには限(かぎ)られた数(通常(つうじょう)3個(こ)程度(ていど))のトークンがあり、検証(けんしょう)されていない技術(ぎじゅつ)を導入(どうにゅう)するたびに1つ消費(しょうひ)します。

節約(せつやく)すべき理由(りゆう):スタートアップのビジネスモデル自体(じたい)がすでに1つのイノベーションなのでトークン1個(こ)を使用(しよう)します。残(のこ)りのトークンを技術(ぎじゅつ)スタックですべて消費(しょうひ)すると、問題(もんだい)発生時(はっせいじ)に対応(たいおう)する余力(よりょく)がなくなります。

Q2: MVP段階でなぜNext.js + Supabase + Vercelの組み合わせが推奨されるのですか?

A: この組(く)み合(あ)わせが推奨(すいしょう)される理由(りゆう):

  1. 開発(かいはつ)速度(そくど): Next.jsのフルスタック機能(きのう)でフロント/バック分離(ぶんり)なしに開発(かいはつ)可能(かのう)
  2. コスト: 3つのサービスすべてに寛大(かんだい)な無料(むりょう)ティアあり(月額(げつがく)0〜50ドル)
  3. インフラ管理(かんり)ゼロ: VercelがデプロイからCDN、SSLまですべて処理(しょり)、SupabaseがDB、Auth、Storageを処理(しょり)
  4. 型(かた)安全性(あんぜんせい): TypeScriptでフルスタックの型(かた)共有(きょうゆう)
  5. 拡張性(かくちょうせい): SupabaseはPostgreSQLベースなので将来(しょうらい)のマイグレーションが容易(ようい)

核心(かくしん)はPMFを見(み)つける前(まえ)にインフラに時間(じかん)を使(つか)わないことです。

Q3: PaaS(Vercel)からIaaS(AWS)への移行はいつすべきですか?

A: 移行(いこう)判断(はんだん)基準(きじゅん):

  1. コスト: Vercelのコストが月(つき)500ドルを超(こ)える時(とき)
  2. 技術的(ぎじゅつてき)制約(せいやく): 5分(ぷん)以上(いじょう)のロングランニングプロセス、WebSocketベースのリアルタイム機能(きのう)、GPUコンピューティングが必要(ひつよう)な時(とき)
  3. 規制(きせい): データ保存(ほぞん)場所(ばしょ)の規制(きせい)、SOC 2/HIPAA認証(にんしょう)、VPC隔離(かくり)が必要(ひつよう)な時(とき)
  4. チーム: DevOps/インフラ専任(せんにん)人員(じんいん)が確保(かくほ)された時(とき)

核心(かくしん)は「必要(ひつよう)な時(とき)に」移行(いこう)することであり、事前(じぜん)に準備(じゅんび)することではありません。

Q4: スタートアップが最初からマイクロサービスを採用すべきでない理由は?

A: 最初(さいしょ)からマイクロサービスを導入(どうにゅう)すべきでない理由(りゆう):

  1. サービス境界(きょうかい)の失敗(しっぱい): PMF前(まえ)はドメインが常(つね)に変化(へんか)するため、サービス境界(きょうかい)を正(ただ)しく定(さだ)めることが不可能(ふかのう)
  2. 運用(うんよう)オーバーヘッド: サービス間(かん)通信(つうしん)、分散(ぶんさん)トランザクション、サービスディスカバリーなどの複雑性(ふくざつせい)が爆発(ばくはつ)
  3. デバッグの困難(こんなん)さ: 分散(ぶんさん)システムのデバッグはモノリスの10倍(ばい)困難(こんなん)
  4. インフラコスト: 各(かく)サービスに個別(こべつ)のデプロイパイプライン、モニタリング、ロギングが必要(ひつよう)(コスト3倍(ばい)以上(いじょう))
  5. チーム規模(きぼ)のミスマッチ: 3〜5名(めい)のチームが10サービスを管理(かんり)するのは非現実的(ひげんじつてき)

正(ただ)しいアプローチ:モノリスで開始(かいし)し、ドメインの理解(りかい)が十分(じゅうぶん)になった後(あと)、ボトルネックのみを選択的(せんたくてき)にサービス分離(ぶんり)。

Q5: 技術スタックの選択は採用にどのような影響を与えますか?

A: 技術(ぎじゅつ)スタックは採用(さいよう)に直接的(ちょくせつてき)な影響(えいきょう)を与(あた)えます:

  1. 採用(さいよう)プールの規模(きぼ): React/TypeScript開発者(かいはつしゃ)は非常(ひじょう)に多(おお)いが、Rust/Elixir開発者(かいはつしゃ)は極少数(ごくしょうすう)
  2. 採用(さいよう)速度(そくど): 人気(にんき)のスタックは応募者(おうぼしゃ)が多(おお)く、迅速(じんそく)な採用(さいよう)が可能(かのう)
  3. 採用(さいよう)コスト: 希少(きしょう)な技術(ぎじゅつ)にはプレミアム年俸(ねんぽう)が必要(ひつよう)
  4. オンボーディング: 既知(きち)の技術(ぎじゅつ)なら1週間(しゅうかん)以内(いない)、新(あたら)しい技術(ぎじゅつ)なら1ヶ月(かげつ)以上(いじょう)
  5. 離職率(りしょくりつ): 市場(しじょう)で人気(にんき)のない技術(ぎじゅつ)はキャリア不安(ふあん)から離職率(りしょくりつ)が上昇(じょうしょう)

推奨(すいしょう): TypeScript + React + PostgreSQL + AWSは最(もっと)も採用(さいよう)しやすい組(く)み合(あ)わせです。


参考(さんこう)資料(しりょう)

  1. McKinley, D. "Choose Boring Technology." https://boringtechnology.club/
  2. Fowler, M. "Monolith First." Martin Fowler's blog.
  3. Next.js Documentation (2025). https://nextjs.org/docs
  4. Supabase Documentation. https://supabase.com/docs
  5. Vercel Documentation. https://vercel.com/docs
  6. Stripe Documentation. https://stripe.com/docs
  7. Terraform AWS Provider Documentation.
  8. "The Twelve-Factor App." https://12factor.net/
  9. Kleppmann, M. "Designing Data-Intensive Applications." O'Reilly.
  10. Newman, S. "Building Microservices, 2nd Edition." O'Reilly.
  11. Basecamp. "Getting Real." https://basecamp.com/gettingreal
  12. Linear Engineering Blog. https://linear.app/blog
  13. Cal.com GitHub Repository. https://github.com/calcom/cal.com
  14. AWS Startup Guides. https://aws.amazon.com/startups/
  15. Google for Startups Cloud Program. https://cloud.google.com/startup
  16. Y Combinator Library - Technical Decisions. https://www.ycombinator.com/library