DEV Community

スシロー
スシロー

Posted on

【実体験】Vercel/Supabase無料枠で月数百円に抑える構成と落とし穴

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 $$
);
Enter fullscreen mode Exit fullscreen mode

または 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
Enter fullscreen mode Exit fullscreen mode

落とし穴 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)
Enter fullscreen mode Exit fullscreen mode

vercel.json で Function の maxDuration を明示する。

{
  "functions": {
    "api/**/*.ts": {
      "maxDuration": 10
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

落とし穴 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);
Enter fullscreen mode Exit fullscreen mode

supabase db diff でポリシー差分をローカルで確認する習慣をつける。

npx supabase db diff --schema public
Enter fullscreen mode Exit fullscreen mode

推奨構成:月0円に抑える最小スタック

Next.js (App Router)
  └─ Static Pages      → Vercel CDN (帯域ほぼ消費しない)
  └─ API Routes        → Vercel Functions (100k回/月で十分)
        └─ Supabase    → PostgreSQL + Auth + Storage
Enter fullscreen mode Exit fullscreen mode

帯域を節約するキャッシュ設定

// next.config.js
module.exports = {
  async headers() {
    return [
      {
        source: '/api/:path*',
        headers: [
          { key: 'Cache-Control', value: 's-maxage=60, stale-while-revalidate=300' },
        ],
      },
    ]
  },
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

監視:超過前に気づく

Vercel は Usage ページに閾値アラートがないため、手動で確認するか Vercel API で取得する。

# Vercel 使用量を確認
curl -s "https://api.vercel.com/v2/usage" \
  -H "Authorization: Bearer $VERCEL_TOKEN" | jq '.bandwidth.used'
Enter fullscreen mode Exit fullscreen mode

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)