Skip to content

Cloudflare R2:零 Egress 費用的 S3 替代方案

2026年3月27日 1 分鐘
TL;DR R2 是 Cloudflare 的物件儲存,S3 相容 API、零 egress 費用、Workers 原生 binding。媒體密集的應用不用再擔心流量帳單。

如果你的應用跑在 Cloudflare Workers 上,你需要地方存圖片——用 S3 可以,但要管 AWS 帳號、設 CORS、還要注意 egress 費用(AWS 從 S3 傳資料出去要收費,流量大了帳單很嚇人)。R2 解決了這個問題:S3 相容 API、零 egress 費用、跑在 Cloudflare 網路上。

R2 是什麼

R2 是 Cloudflare 的物件儲存服務,設計來取代 S3。對 Cloudflare Workers 生態來說,R2 是最自然的選擇。

和 S3 的主要差異

AWS S3Cloudflare R2
API 相容性原版S3 相容(Drop-in replacement)
Egress 費用每 GB 收費(0.09 USD/GB)免費
儲存費用0.023 USD/GB0.015 USD/GB
CDN 整合需要另設 CloudFront直接走 Cloudflare CDN
Workers 整合需要 SDK + 額外延遲原生 binding,低延遲

Egress 免費這點對媒體密集的應用來說很重要。攀岩紀錄有大量圖片、影片縮圖,如果存在 S3 上,每個人打開照片都要付 egress——R2 省掉這塊。

基本用法(Cloudflare Workers)

wrangler.toml 綁定設定

[[r2_buckets]]
binding = "BUCKET"
bucket_name = "nobodyclimb-media"

Worker 裡操作 R2

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
    const key = url.pathname.slice(1); // /images/foo.jpg → images/foo.jpg

    if (request.method === 'PUT') {
      await env.BUCKET.put(key, request.body, {
        httpMetadata: {
          contentType: request.headers.get('content-type') ?? 'application/octet-stream',
        },
      });
      return new Response('Uploaded', { status: 200 });
    }

    if (request.method === 'GET') {
      const object = await env.BUCKET.get(key);
      if (!object) return new Response('Not Found', { status: 404 });

      return new Response(object.body, {
        headers: {
          'content-type': object.httpMetadata?.contentType ?? 'application/octet-stream',
          'cache-control': 'public, max-age=31536000', // 圖片 1 年快取
        },
      });
    }

    if (request.method === 'DELETE') {
      await env.BUCKET.delete(key);
      return new Response('Deleted', { status: 200 });
    }

    return new Response('Method Not Allowed', { status: 405 });
  },
};

透過 S3 相容 API 存取

R2 也支援 AWS SDK,方便從 Next.js server action 或其他服務上傳:

import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';

const s3 = new S3Client({
  region: 'auto',
  endpoint: `https://${ACCOUNT_ID}.r2.cloudflarestorage.com`,
  credentials: {
    accessKeyId: R2_ACCESS_KEY_ID,
    secretAccessKey: R2_SECRET_ACCESS_KEY,
  },
});

const command = new PutObjectCommand({
  Bucket: 'nobodyclimb-media',
  Key: `climbs/${climbId}/photo.jpg`,
  Body: imageBuffer,
  ContentType: 'image/jpeg',
});

await s3.send(command);

NobodyClimb 怎麼用 R2

NobodyClimb 是全 Cloudflare 架構,R2 負責所有媒體儲存:

使用者上傳照片(攀登紀錄、故事配圖)


Hono API(驗證 + 產生 upload key)


      R2 Bucket
    nobodyclimb-media/
    ├── climbs/{climbId}/
    │   ├── photo-original.jpg
    │   └── photo-thumb.jpg      ← 縮圖,影片預覽圖
    └── stories/{storyId}/
        └── cover.jpg


Cloudflare CDN(圖片有 cache-control,全球快取)

原始圖存一份,thumbnail 另外存——thumbnail 是頁面載入時顯示的,要快,原始圖只有點開才用,可以接受慢一點。

取捨

優點

  • 零 egress 費用,媒體密集應用省很多
  • S3 相容,遷移成本低
  • Workers binding 原生,低延遲
  • 自動走 Cloudflare CDN

缺點

  • 不如 S3 生態成熟(Lambda trigger、生命週期規則功能較少)
  • 地理位置選擇比 S3 少
  • 大型組織可能還是需要 AWS 生態的深度整合

什麼時候選 R2

  • 已經選定 Cloudflare Workers 作為運算平台
  • 媒體密集(圖片、影片縮圖),egress 費用是考量
  • 規模中等,不需要 AWS 的企業級功能

如果你沒有用 Workers,S3 可能更合適。R2 的最大價值在於跟 Cloudflare 生態的無縫整合,拆出來單獨用優勢就小很多。

參考資料