Vercel/Supabase無料枠で月数百円に抑える構成と落とし穴
結論
正しく設定すれば Vercel + Supabase の月額コストはほぼ¥0 で運用できる。ただし「無料枠を超えた瞬間に課金が走る」罠が複数あり、何も考えずに使うと月数千円になる。この記事では具体的な制限値と、超過を防ぐための設定を示す。
無料枠の実際の数字
Vercel (Hobby Plan)
| 項目 | 上限 |
|---|---|
| Bandwidth | 100 GB/月 |
| Function Invocations | 100,000 回/月 |
| Function Duration | 10秒/リクエスト |
| Deployments | 無制限 |
Supabase (Free Plan)
| 項目 | 上限 |
|---|---|
| DB Storage | 500 MB |
| Bandwidth | 5 GB/月 |
| API Requests | 500,000 回/月 |
| Edge Functions | 500,000 回/月 |
| Realtime Connections | 同時200接続 |
| アクティブでないと | 7日で一時停止 |
落とし穴 1:Supabase の自動停止
無料プロジェクトは7日間アクセスがないと自動停止する。
対策は pg_cron で定期的にダミークエリを投げること。
-- Supabase SQL Editor で実行
select cron.schedule(
'keep-alive',
'0 */6 * * *', -- 6時間ごと
$$ select 1 $$
);
または GitHub Actions で外部から叩く。
# .github/workflows/keep-alive.yml
name: Supabase Keep Alive
on:
schedule:
- cron: '0 */6 * * *'
jobs:
ping:
runs-on: ubuntu-latest
steps:
- run: |
curl -s "${{ secrets.SUPABASE_URL }}/rest/v1/health" \
-H "apikey: ${{ secrets.SUPABASE_ANON_KEY }}" > /dev/null
落とし穴 2:Vercel の Serverless Function が無限ループ
Edge/Serverless Function 内で外部APIを叩くとき、タイムアウト設定を忘れると 10秒制限に引っかかり429/500が頻発してリトライが積み重なる。
// NG: タイムアウト未設定
const res = await fetch('https://api.example.com/data')
// OK: AbortController でタイムアウト
const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), 8000) // 8秒
const res = await fetch('https://api.example.com/data', {
signal: controller.signal,
})
clearTimeout(timeout)
vercel.json で Function の maxDuration を明示する。
{
"functions": {
"api/**/*.ts": {
"maxDuration": 10
}
}
}
落とし穴 3:Supabase の Row Level Security を切ると全データが漏洩
無料枠・有料枠に関係なく、RLS を無効にしたままテーブルを公開すると anon キーで全行 SELECT される。
-- 必ず RLS を有効にする
alter table posts enable row level security;
-- 認証済みユーザーのみ自分のデータを読める
create policy "own data only"
on posts for select
using (auth.uid() = user_id);
supabase db diff でポリシー差分をローカルで確認する習慣をつける。
npx supabase db diff --schema public
推奨構成:月0円に抑える最小スタック
Next.js (App Router)
└─ Static Pages → Vercel CDN (帯域ほぼ消費しない)
└─ API Routes → Vercel Functions (100k回/月で十分)
└─ Supabase → PostgreSQL + Auth + Storage
帯域を節約するキャッシュ設定
// next.config.js
module.exports = {
async headers() {
return [
{
source: '/api/:path*',
headers: [
{ key: 'Cache-Control', value: 's-maxage=60, stale-while-revalidate=300' },
],
},
]
},
}
Supabase Storage の画像は変換APIを使わない
/storage/v1/render/image/ の変換エンドポイントは 変換1回ごとにカウントされる。無料枠では素の URL を返してフロントで <img width> リサイズに留めること。
// NG: 変換API経由 → リクエスト消費
const url = supabase.storage.from('images').getPublicUrl('photo.jpg', {
transform: { width: 400 }
}).data.publicUrl
// OK: オリジナルURLをそのまま返してHTMLでリサイズ
const url = supabase.storage.from('images').getPublicUrl('photo.jpg').data.publicUrl
監視:超過前に気づく
Vercel は Usage ページに閾値アラートがないため、手動で確認するか Vercel API で取得する。
# Vercel 使用量を確認
curl -s "https://api.vercel.com/v2/usage" \
-H "Authorization: Bearer $VERCEL_TOKEN" | jq '.bandwidth.used'
Supabase はダッシュボード Project Settings > Usage で確認する。月15日頃に半分を超えていたら投稿頻度を下げる。
まとめ
- Supabase は7日keep-aliveを仕込む(自動停止が最大の罠)
- Function は8秒タイムアウト必須(リトライ爆発を防ぐ)
- RLS は絶対に無効にしない
- Storage 変換APIは使わない
- 帯域は Cache-Control で削る
この4点を守れば個人サービスは月¥0で十分動く。スケールして無料枠を超えたときは、Supabase Pro($25/月)より先に 不要なリアルタイム接続とStorageトリガーを削ることで延命できる。
関連リンク
※自社商品(プロモーションを含みます)。
Top comments (0)