Vercel 帳單暴增?5 個防護策略|2025 費用控制完整指南

Vercel 帳單暴增?5 個防護策略|2025 費用控制完整指南

一覺醒來,Vercel 帳單多了幾百美元。

這種噩夢確實發生過。

DDoS 攻擊、爬蟲、病毒式傳播...

各種原因都可能讓帳單失控。

這篇文章教你如何保護你的帳單。


Vercel 計費項目

費用組成

項目 免費額度 超出費用
頻寬 100 GB $0.15/GB
Serverless 執行 100 GB-hrs $0.18/GB-hr
Edge 請求 500,000 次 $0.65/百萬次
Edge 執行 1,000,000 GB-s $0.018/GB-hr
Image Optimization 1,000 張 $5/1,000 張
Build 分鐘 6,000 分鐘 $0.01/分鐘

費用計算範例

假設你的網站突然爆紅:

一天 100 萬次訪問
- 頻寬:100 萬 × 500KB = 500 GB
- 超出:500 - 100 = 400 GB
- 費用:400 × $0.15 = $60/天

一個月:$60 × 30 = $1,800

加上 Serverless Functions、Edge Functions...

帳單可能更高。


策略一:設定 Spend Management

什麼是 Spend Management?

Vercel 提供的費用控制功能,可以設定預算上限。

Pro 和 Enterprise 方案可用。

設定步驟

  1. 進入 Vercel Dashboard
  2. 點擊 SettingsBilling
  3. 選擇 Spend Management
  4. 設定每月預算上限

設定選項

選項 說明
Hard Limit 達到上限後停止服務
Soft Limit 達到上限後發送通知

建議設定:

  • Soft Limit:預算的 80%
  • Hard Limit:預算的 100%

達到上限會怎樣?

Hard Limit 達到時:

  • Serverless Functions 停止執行
  • 靜態資源仍可訪問
  • 新部署被阻止

恢復方法:

  1. 提高上限
  2. 等待下個計費週期

策略二:實作 API 限流

為什麼需要限流?

防止:

  • 單一用戶過度使用
  • API 被濫用
  • 惡意攻擊

使用 Upstash Rate Limit

npm install @upstash/ratelimit @upstash/redis
// lib/ratelimit.ts
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';

export const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, '10 s'), // 每 10 秒 10 次
  analytics: true,
});
// app/api/protected/route.ts
import { ratelimit } from '@/lib/ratelimit';
import { headers } from 'next/headers';

export async function GET(request: Request) {
  // 使用 IP 作為識別
  const ip = headers().get('x-forwarded-for') ?? '127.0.0.1';

  const { success, limit, remaining, reset } = await ratelimit.limit(ip);

  if (!success) {
    return Response.json(
      { error: '請求過於頻繁,請稍後再試' },
      {
        status: 429,
        headers: {
          'X-RateLimit-Limit': limit.toString(),
          'X-RateLimit-Remaining': remaining.toString(),
          'X-RateLimit-Reset': reset.toString(),
        },
      }
    );
  }

  // 正常處理
  return Response.json({ data: 'success' });
}

不同層級的限流

// 針對不同端點設定不同限制
const publicLimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(100, '1 m'), // 公開 API
});

const authLimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(1000, '1 m'), // 已認證用戶
});

const aiLimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, '1 m'), // AI API(昂貴)
});

策略三:使用 Middleware 防護

全站防護

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
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 ip = request.ip ?? '127.0.0.1';
    const { success, limit, remaining } = await ratelimit.limit(ip);

    if (!success) {
      return NextResponse.json(
        { error: 'Too many requests' },
        { status: 429 }
      );
    }
  }

  return NextResponse.next();
}

export const config = {
  matcher: '/api/:path*',
};

阻擋可疑 IP

// middleware.ts
const blockedIPs = new Set([
  '1.2.3.4',
  '5.6.7.8',
]);

const blockedCountries = new Set([
  'XX', // 可疑國家
]);

export async function middleware(request: NextRequest) {
  const ip = request.ip ?? '127.0.0.1';
  const country = request.geo?.country ?? '';

  // 阻擋可疑 IP
  if (blockedIPs.has(ip)) {
    return new Response('Forbidden', { status: 403 });
  }

  // 阻擋可疑國家
  if (blockedCountries.has(country)) {
    return new Response('Forbidden', { status: 403 });
  }

  return NextResponse.next();
}

阻擋爬蟲

// middleware.ts
const blockedUserAgents = [
  'bot',
  'crawler',
  'spider',
  'scraper',
];

export async function middleware(request: NextRequest) {
  const userAgent = request.headers.get('user-agent')?.toLowerCase() ?? '';

  // 阻擋可疑 User-Agent
  if (blockedUserAgents.some(agent => userAgent.includes(agent))) {
    // 允許好的爬蟲
    if (userAgent.includes('googlebot') || userAgent.includes('bingbot')) {
      return NextResponse.next();
    }
    return new Response('Forbidden', { status: 403 });
  }

  return NextResponse.next();
}

策略四:監控和告警

設定用量告警

在 Vercel Dashboard:

  1. Settings → Notifications
  2. 新增告警規則
  3. 設定觸發條件

告警類型:

  • 用量達到 X%
  • 費用達到 $X
  • 異常流量檢測

自建監控

// lib/monitoring.ts
import { kv } from '@vercel/kv';

export async function trackUsage(event: string) {
  const today = new Date().toISOString().split('T')[0];
  const key = `usage:${event}:${today}`;

  const count = await kv.incr(key);

  // 檢查是否超過閾值
  if (count > 10000) {
    await sendAlert(`High usage detected: ${event} = ${count}`);
  }

  return count;
}

async function sendAlert(message: string) {
  // 發送 Slack 通知
  await fetch(process.env.SLACK_WEBHOOK_URL!, {
    method: 'POST',
    body: JSON.stringify({ text: `🚨 ${message}` }),
  });

  // 或發送郵件
  // await sendEmail(process.env.ALERT_EMAIL!, message);
}

使用範例

// app/api/expensive/route.ts
import { trackUsage } from '@/lib/monitoring';

export async function POST(request: Request) {
  // 追蹤使用量
  await trackUsage('expensive-api');

  // 處理請求
  return Response.json({ success: true });
}

每日報告

// app/api/cron/daily-report/route.ts
export async function GET() {
  const today = new Date().toISOString().split('T')[0];

  const stats = {
    apiCalls: await kv.get(`usage:api:${today}`) || 0,
    aiCalls: await kv.get(`usage:ai:${today}`) || 0,
    imageCalls: await kv.get(`usage:image:${today}`) || 0,
  };

  await fetch(process.env.SLACK_WEBHOOK_URL!, {
    method: 'POST',
    body: JSON.stringify({
      text: `📊 Daily Report for ${today}\n` +
            `API Calls: ${stats.apiCalls}\n` +
            `AI Calls: ${stats.aiCalls}\n` +
            `Image Calls: ${stats.imageCalls}`,
    }),
  });

  return Response.json({ sent: true });
}
// vercel.json
{
  "crons": [
    {
      "path": "/api/cron/daily-report",
      "schedule": "0 9 * * *"
    }
  ]
}

策略五:優化資源使用

減少頻寬使用

啟用壓縮:

// next.config.js
module.exports = {
  compress: true,
};

使用 WebP 圖片:

import Image from 'next/image';

<Image
  src="/photo.jpg"
  width={800}
  height={600}
  quality={75} // 降低品質
  format="webp"
/>

設定快取標頭:

// vercel.json
{
  "headers": [
    {
      "source": "/static/(.*)",
      "headers": [
        {
          "key": "Cache-Control",
          "value": "public, max-age=31536000, immutable"
        }
      ]
    }
  ]
}

減少 Serverless 執行時間

// 使用快取減少執行
import { unstable_cache } from 'next/cache';

const getCachedData = unstable_cache(
  async () => {
    return await expensiveQuery();
  },
  ['data-key'],
  { revalidate: 300 }
);

export async function GET() {
  const data = await getCachedData();
  return Response.json(data);
}

減少 Image Optimization

// next.config.js
module.exports = {
  images: {
    // 限制優化的圖片來源
    remotePatterns: [
      { hostname: 'trusted-cdn.com' },
    ],
    // 或完全關閉
    unoptimized: true,
  },
};

使用 Edge Functions

Edge Functions 比 Serverless Functions 便宜:

項目 Serverless Edge
計費單位 GB-hr 請求數
費用 $0.18/GB-hr $0.65/百萬次

對於簡單操作,Edge 更划算。

// app/api/fast/route.ts
export const runtime = 'edge';

export async function GET() {
  return Response.json({ fast: true });
}

緊急應對:帳單已經暴增

立即行動

  1. 聯繫 Vercel 客服
    - 說明情況
    - 可能獲得減免

  2. 暫停服務
    - 設定 Hard Limit
    - 暫時下線

  3. 分析原因
    - 查看 Analytics
    - 檢查 Logs

常見原因和解決

原因 解決方法
DDoS 攻擊 啟用 Cloudflare
爬蟲 設定 robots.txt
病毒式傳播 設定限流
API 濫用 加強認證
程式錯誤 修復並重新部署

預防再次發生

  1. 設定 Spend Management
  2. 實作限流
  3. 設定監控告警
  4. 定期檢查用量

免費方案的安全使用

免費額度

項目 額度
頻寬 100 GB
Serverless 100 GB-hrs
Build 6,000 分鐘

如何不超額?

預估用量:

100 GB 頻寬 ÷ 500 KB/頁面 = 200,000 頁面訪問

如果每天 6,000 訪問:
6,000 × 30 = 180,000/月

應該夠用!

如果快超額:

  1. 優化資源大小
  2. 增加快取
  3. 減少 API 呼叫
  4. 考慮升級

帳單檢查清單

每週檢查

  • [ ] 查看 Usage 頁面
  • [ ] 確認沒有異常流量
  • [ ] 檢查 Logs 有無錯誤

每月檢查

  • [ ] 檢視帳單明細
  • [ ] 分析各項目費用
  • [ ] 優化高費用項目
  • [ ] 更新限流設定

設定完成

  • [ ] Spend Management 已設定
  • [ ] 限流已實作
  • [ ] 監控告警已啟用
  • [ ] 緊急聯絡方式已準備

常見問題 FAQ

Q1:免費方案會有帳單嗎?

不會,Hobby 方案免費。

但如果升級到 Pro 且沒設定 Spend Management,可能會有意外費用。

Q2:設定 Hard Limit 後網站會掛嗎?

靜態資源仍可訪問,但 Serverless Functions 會停止。

建議設定 Soft Limit 先收到通知。

Q3:如何知道是否被攻擊?

  1. 流量突然暴增
  2. 來源 IP 集中
  3. 請求模式異常

Q4:Vercel 會退費嗎?

視情況而定。

如果是因為攻擊或平台問題,可以聯繫客服協商。

Q5:應該用 Cloudflare 嗎?

推薦使用 Cloudflare 作為額外防護:

  • 免費 CDN
  • DDoS 防護
  • 可設定防火牆規則

Vercel 帳單防護五大策略重點整理

5 個防護策略:

  1. Spend Management - 設定預算上限
  2. API 限流 - 限制請求頻率
  3. Middleware 防護 - 阻擋惡意流量
  4. 監控告警 - 及時發現問題
  5. 優化資源 - 減少不必要費用

做好這些,帳單就在你的掌控中。


Vercel 部署失敗?

Build Error、環境變數、自訂網域,我們幫你快速排除問題。

解決 Vercel 問題


延伸閱讀

分享文章:
V

VibeFix

專門解決 AI Vibe Coding 後的疑難雜症,讓你的專案順利上線。

這篇文章有幫到你嗎?

如果還有問題,讓我們直接幫你解決!

聯繫我們