1年前にリリースした個人サービスが、ありがたいことに DAU 1000人前後で安定運用できている。驚くのは、このサービスの月額インフラコストが 480円 だということだ。本稿ではその構成と、どこにいくら払っているかを全部公開する。数字は執筆時点で実際に請求されている額だ。
サービス概要
- ジャンル: エンジニア向け情報集約ツール
- DAU: 約 1000
- MAU: 約 8000
- 月間PV: 約 45万
- 技術スタック: Next.js + Cloudflare + Supabase
月額コスト内訳
| 項目 | サービス | 金額 |
|---|---|---|
| 独自ドメイン | お名前.com | 月 120円 (年1430円 / 12) |
| DNS | Cloudflare | 0円 |
| フロント | Cloudflare Pages | 0円 |
| API | Cloudflare Workers Paid | 月 360円 ($5 / 月を日本円換算) |
| DB | Supabase Free | 0円 |
| 画像配信 | Cloudflare R2 | 0円 (無料枠内) |
| メール | Resend Free | 0円 |
| 分析 | Plausible (self-host) | Workers と同居 / 実質0円 |
| 合計 | 月 480円 |
構成図
[User]
|
v
[Cloudflare DNS] --> [Cloudflare Pages]
|
v
[Cloudflare Workers (Hono)]
|
+---------------+---------------+
v v v
[Supabase DB] [R2 (画像)] [Resend (通知)]
なぜこの構成にしたか
Pages + Workers の分離
最初はすべて Next.js の Vercel デプロイで動かしていたが、帯域上限が怖かったので Pages へ寄せた。ISR が必要な部分だけ Workers で返す構成にしている。
Supabase Free のまま
DB サイズは執筆時点で 230MB、Auth は MAU 8000 なので無料枠の範囲内。バックアップだけは自前で GitHub Actions で日次取得している。
# .github/workflows/backup.yml
name: daily-backup
on:
schedule:
- cron: '15 18 * * *' # UTC 18:15 = JST 03:15
jobs:
backup:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Dump DB
env:
PGURL: ${{ secrets.SUPABASE_DB_URL }}
run: |
sudo apt-get install -y postgresql-client
pg_dump "$PGURL" > dump-$(date +%Y%m%d).sql
- name: Upload to R2
env:
R2_KEY: ${{ secrets.R2_ACCESS_KEY }}
R2_SECRET: ${{ secrets.R2_SECRET }}
run: |
# aws-cli で R2 にアップロード
aws s3 cp dump-*.sql s3://my-backup/ --endpoint-url $R2_ENDPOINT
画像は R2 に置く
Supabase Storage は無料枠 1GB とやや狭いので、ユーザー投稿画像は R2 に逃がしてある。R2 はエグレス無料なので、いくら配信されても追加料金が発生しない。
API の中身
import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { rateLimiter } from 'hono/rate-limiter'
const app = new Hono()
app.use('*', cors({ origin: ['https://example.com'] }))
app.use('*', rateLimiter({ windowMs: 60_000, limit: 60 }))
app.get('/feed', async (c) => {
// キャッシュにヒットすれば DB を叩かない
const cached = await c.env.CACHE.get('feed', 'json')
if (cached) return c.json(cached)
const res = await fetchFromSupabase(c.env)
await c.env.CACHE.put('feed', JSON.stringify(res), { expirationTtl: 120 })
return c.json(res)
})
export default app
KV 2分キャッシュを挟んだだけで、Workers のリクエスト消費が半減した。
月次の観察
- Workers リクエスト: 月 450万
- D1/Supabase 読み取り: 月 200万
- R2 Class A: 月 8万
- R2 ストレージ: 執筆時点で 2.3GB
Workers の月 450万リクエストは Paid プランの範囲内に収まる。
コスト試算と将来予測
DAU 5000 までなら、この構成のまま月額 480円で行ける見積もりだ。DAU 1万を超えたあたりで Supabase を Pro (月25ドル) に上げる必要が出てくる。逆に言えば、5倍スケールするまで構成を変えなくてよい。
まとめ
- 個人開発は500円以下で DAU 1000を回せる
- 画像配信は R2 一択
- キャッシュ層を入れるだけで DB とAPIの負荷は劇的に下がる
「サーバー代が怖いから個人開発に踏み出せない」というのは、2026年の現実には合わない心配だ。