Cloudflare R2 物件儲存:零出口費用的 S3 替代方案
Cloudflare R2 物件儲存:零出口費用的 S3 替代方案
AWS S3 是業界標準的物件儲存服務,但它有一個讓人頭痛的費用結構:出口費用(Egress)。當你的資料從 S3 傳輸到外部時,每 GB 都要收費。對於高流量的應用,這個費用可能非常可觀。Cloudflare R2 提供了 S3 相容的 API,而且完全不收出口費用。
R2 的費用結構
| 服務 | 儲存費用 | 出口費用 |
|---|---|---|
| AWS S3 | $0.023/GB | $0.09/GB |
| Cloudflare R2 | $0.015/GB | $0(零!) |
前 10 GB 儲存和每月前 1000 萬次 A 類操作免費。
建立 R2 Bucket
透過 Cloudflare Dashboard
- 登入 Cloudflare Dashboard
- 前往 R2 Object Storage
- 點擊 "Create bucket"
- 輸入 Bucket 名稱和選擇地區
透過 Wrangler CLI
# 安裝 Wrangler
npm install -g wrangler
# 登入
wrangler login
# 建立 Bucket
wrangler r2 bucket create my-files
# 列出 Bucket
wrangler r2 bucket list取得 API 憑證
Cloudflare Dashboard → R2 → Manage R2 API Tokens
→ Create API Token
→ 選擇權限(Object Read & Write)
→ 記下 Access Key ID 和 Secret Access Key使用 AWS SDK 操作 R2
R2 完全相容 S3 API,可以直接用 AWS SDK:
import { S3Client, PutObjectCommand, GetObjectCommand,
DeleteObjectCommand, ListObjectsV2Command } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
const r2 = new S3Client({
region: 'auto',
endpoint: `https://${process.env.CF_ACCOUNT_ID}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
},
});
const BUCKET = 'my-files';
// 上傳檔案
async function uploadFile(key: string, body: Buffer | Uint8Array, contentType: string) {
await r2.send(new PutObjectCommand({
Bucket: BUCKET,
Key: key,
Body: body,
ContentType: contentType,
}));
return key;
}
// 下載檔案
async function downloadFile(key: string): Promise<Buffer> {
const response = await r2.send(new GetObjectCommand({
Bucket: BUCKET,
Key: key,
}));
const chunks: Uint8Array[] = [];
for await (const chunk of response.Body as AsyncIterable<Uint8Array>) {
chunks.push(chunk);
}
return Buffer.concat(chunks);
}
// 刪除檔案
async function deleteFile(key: string) {
await r2.send(new DeleteObjectCommand({
Bucket: BUCKET,
Key: key,
}));
}
// 列出檔案
async function listFiles(prefix?: string) {
const response = await r2.send(new ListObjectsV2Command({
Bucket: BUCKET,
Prefix: prefix,
MaxKeys: 100,
}));
return response.Contents || [];
}
// 生成預簽名 URL(有時效的下載連結)
async function getPresignedUrl(key: string, expiresIn = 3600): Promise<string> {
return getSignedUrl(
r2,
new GetObjectCommand({ Bucket: BUCKET, Key: key }),
{ expiresIn }
);
}在 Next.js API Route 中使用
// app/api/upload/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { r2, BUCKET } from '@/lib/r2';
import { PutObjectCommand } from '@aws-sdk/client-s3';
import { nanoid } from 'nanoid';
export async function POST(req: NextRequest) {
const formData = await req.formData();
const file = formData.get('file') as File;
if (!file) {
return NextResponse.json({ error: '未找到檔案' }, { status: 400 });
}
const maxSize = 10 * 1024 * 1024; // 10MB
if (file.size > maxSize) {
return NextResponse.json({ error: '檔案大小超過限制' }, { status: 400 });
}
const ext = file.name.split('.').pop();
const key = `uploads/${nanoid()}.${ext}`;
const buffer = Buffer.from(await file.arrayBuffer());
await r2.send(new PutObjectCommand({
Bucket: BUCKET,
Key: key,
Body: buffer,
ContentType: file.type,
Metadata: {
originalName: file.name,
},
}));
const url = `${process.env.R2_PUBLIC_URL}/${key}`;
return NextResponse.json({ url, key });
}設定自訂網域(公開 Bucket)
讓 R2 檔案可以透過你的網域直接存取:
Cloudflare Dashboard → R2 → 你的 Bucket → Settings
→ Public Access → Connect Domain
→ 輸入子網域(如 files.example.com)這樣 R2 中的檔案就可以透過 https://files.example.com/path/to/file.jpg 存取,且透過 Cloudflare CDN 分發,速度快,出口費用為零。
在 Cloudflare Workers 中直接使用
Workers 可以直接綁定 R2,無需 HTTP 請求:
// wrangler.toml
// [[r2_buckets]]
// binding = "MY_BUCKET"
// bucket_name = "my-files"
export interface Env {
MY_BUCKET: R2Bucket;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
const key = url.pathname.slice(1);
if (request.method === 'GET') {
const object = await env.MY_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=86400',
'ETag': object.etag,
},
});
}
if (request.method === 'PUT') {
await env.MY_BUCKET.put(key, request.body, {
httpMetadata: { contentType: request.headers.get('Content-Type') || '' },
});
return new Response('OK');
}
return new Response('Method Not Allowed', { status: 405 });
},
};從 AWS S3 遷移
R2 的 S3 相容性讓遷移非常簡單,只需修改端點:
// 原本的 S3 設定
const s3 = new S3Client({
region: 'ap-northeast-1',
credentials: { ... },
});
// 改為 R2,只需修改 endpoint 和 region
const r2 = new S3Client({
region: 'auto', // R2 固定使用 'auto'
endpoint: `https://${CF_ACCOUNT_ID}.r2.cloudflarestorage.com`,
credentials: { ... },
});
// 其他程式碼完全不用改!適用場景
R2 的最佳使用場景:
- 使用者上傳的圖片、影片、文件
- 靜態資源(配合 Cloudflare CDN)
- 備份檔案
- 大量下載的資料集
仍選擇 S3 的情況:
- 需要與 AWS 生態深度整合(Lambda、CloudFront OAI)
- 需要特定 S3 進階功能(Object Lock、Intelligent Tiering)
總結
Cloudflare R2 的零出口費用對於高流量應用是巨大的成本優勢。S3 相容 API 讓遷移成本極低,只需修改端點設定。如果你的應用有大量的檔案讀取需求,R2 + Cloudflare CDN 的組合幾乎是目前最划算的物件儲存方案。
分享這篇文章