cloudflare-r2 について
cloudflare-r2 は、Cloudflare R2 オブジェクトストレージと Workers を利用する開発者向けの実践的なスキルです。オブジェクト操作、アップロード方式、アクセス設定、運用上の注意に加え、Wrangler と S3 互換の署名付き URL を解説します。
主な機能
- 01R2 Workers のオブジェクト操作
- 02マルチパートと署名付きアップロード
- 03CORS とメタデータの設定
- 04一括削除と範囲読み取り
- 05Wrangler の設定ガイド
- 06セキュリティと信頼性の実践
活用シーン
- 01大容量または再開可能なファイルのアップロード
- 02署名付き URL によるブラウザーからのアップロード
- 03バケットの CORS とメタデータ設定
- 04ストレージのエラーやレート制限への対応
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jezweb/ claude-skills --skill cloudflare-r2インストール後、Claude Code で/cloudflare-r2を入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
README.md.claude-plugin/plugin.jsonreferences/common-patterns.mds3-compatibility.mdworkers-api.mdrules/cloudflare-r2.mdtemplates/r2-cors-config.jsonr2-multipart-upload.tsr2-presigned-urls.tsr2-simple-upload.ts詳細
Cloudflare R2 オブジェクトストレージ
ステータス:本番環境対応 ✅ 最終更新日:2026-01-20 依存関係:cloudflare-worker-base(Worker のセットアップ用) 最新バージョン:[email protected], @cloudflare/[email protected], [email protected]
最近の更新(2025年):
- 2025年9月:R2 SQL のオープンベータ(Apache Iceberg 向けサーバーレスクエリエンジン)、Pipelines の GA(リアルタイムストリームの取り込み)、リモートバインディングの GA(ローカル開発環境からデプロイ済みの R2 に接続)
- 2025年5月:ダッシュボードの再設計(ディープリンク対応、バケット設定の一元化)、Super Slurper が 5 倍高速化(Workers/Queues/Durable Objects を使って再構築)
- 2025年4月:R2 Data Catalog のオープンベータ(Apache Iceberg のマネージドカタログ)、Event Notifications のオープンベータ(Queue ごとに毎秒 5,000 メッセージ)
- 2025年:バケット上限の引き上げ(最大 100 万)、CRC-64/NVME チェックサム、顧客キーによるサーバー側暗号化、Infrequent Access ストレージクラス(ベータ)、オセアニアリージョン、S3 API の機能強化(sha256/sha1 チェックサム、ListParts、条件付き CopyObject)
クイックスタート(5 分)
# 1. Create bucket
npx wrangler r2 bucket create my-bucket
# 2. Add binding to wrangler.jsonc
# {
# "r2_buckets": [{
# "binding": "MY_BUCKET",
# "bucket_name": "my-bucket",
# "preview_bucket_name": "my-bucket-preview" // Optional: separate dev/prod
# }]
# }
# 3. Upload/download from Worker
type Bindings = { MY_BUCKET: R2Bucket };
// Upload
await env.MY_BUCKET.put('file.txt', data, {
httpMetadata: { contentType: 'text/plain' }
});
// Download
const object = await env.MY_BUCKET.get('file.txt');
if (!object) return c.json({ error: 'Not found' }, 404);
return new Response(object.body, {
headers: {
'Content-Type': object.httpMetadata?.contentType || 'application/octet-stream',
'ETag': object.httpEtag,
},
});
# 4. Deploy
npx wrangler deploy
R2 Workers API
基本メソッド
// put() - Upload objects
await env.MY_BUCKET.put('file.txt', data, {
httpMetadata: {
contentType: 'text/plain',
cacheControl: 'public, max-age=3600',
},
customMetadata: { userId: '123' },
md5: await crypto.subtle.digest('MD5', data), // Checksum verification
});
// Conditional upload (prevent overwrites)
const object = await env.MY_BUCKET.put('file.txt', data, {
onlyIf: { uploadedBefore: new Date('2020-01-01') }
});
if (!object) return c.json({ error: 'File already exists' }, 409);
// get() - Download objects
const object = await env.MY_BUCKET.get('file.txt');
if (!object) return c.json({ error: 'Not found' }, 404);
const text = await object.text(); // As string
const json = await object.json(); // As JSON
const buffer = await object.arrayBuffer(); // As ArrayBuffer
// Range requests (partial downloads)
const partial = await env.MY_BUCKET.get('video.mp4', {
range: { offset: 0, length: 1024 * 1024 } // First 1MB
});
// head() - Get metadata only (no body download)
const object = await env.MY_BUCKET.head('file.txt');
console.log(object.size, object.etag, object.customMetadata);
// delete() - Delete objects
await env.MY_BUCKET.delete('file.txt'); // Single delete (idempotent)
await env.MY_BUCKET.delete(['file1.txt', 'file2.txt']); // Bulk delete (max 1000)
// list() - List objects
const listed = await env.MY_BUCKET.list({
prefix: 'images/', // Filter by prefix
limit: 100,
cursor: cursor, // Pagination
delimiter: '/', // Folder-like listing
include: ['httpMetadata', 'customMetadata'], // IMPORTANT: Opt-in for metadata
});
for (const object of listed.objects) {
console.log(`${object.key}: ${object.size} bytes`);
console.log(object.httpMetadata?.contentType); // Now populated with include parameter
console.log(object.customMetadata); // Now populated with include parameter
}
マルチパートアップロード
100 MB を超えるファイルや、再開可能なアップロードに使用します。大きなファイル、ブラウザーからのアップロード、並列化が必要な場合に利用します。
// 1. Create multipart upload
const multipart = await env.MY_BUCKET.createMultipartUpload('large-file.zip', {
httpMetadata: { contentType: 'application/zip' }
});
// 2. Upload parts (5MB-100MB each, max 10,000 parts)
const multipart = env.MY_BUCKET.resumeMultipartUpload(key, uploadId);
const part1 = await multipart.uploadPart(1, chunk1);
const part2 = await multipart.uploadPart(2, chunk2);
// 3. Complete upload
const object = await multipart.complete([
{ partNumber: 1, etag: part1.etag },
{ partNumber: 2, etag: part2.etag },
]);
// 4. Abort if needed
await multipart.abort();
制限:パートは 5 MB~100 MB、アップロードごとの最大パート数は 10,000 です。5 MB 未満のファイルには使用しないでください(オーバーヘッドが発生します)。
署名付き URL
クライアントが R2 へ直接アップロード/ダウンロードできるようにします(Worker を経由しません)。aws4fetch ライブラリを使用します。
import { AwsClient } from 'aws4fetch';
const r2Client = new AwsClient({
accessKeyId: env.R2_ACCESS_KEY_ID,
secretAccessKey: env.R2_SECRET_ACCESS_KEY,
});
const url = new URL(
`https://${bucketName}.${accountId}.r2.cloudflarestorage.com/${filename}`
);
url.searchParams.set('X-Amz-Expires', '3600'); // 1 hour expiry
const signed = await r2Client.sign(
new Request(url, { method: 'PUT' }), // or 'GET' for downloads
{ aws: { signQuery: true } }
);
// Client uploads directly to R2
await fetch(signed.url, { method: 'PUT', body: file });
重大なセキュリティ上の注意:
- ❌ 絶対に R2 のアクセスキーをクライアント側のコードに公開しないでください
- ✅ 必ず 署名付き URL をサーバー側で生成してください
- ✅ 必ず 有効期限を設定してください(通常は 1~24 時間)
- ✅ 必ず URL を生成する前に認証を追加してください
- ✅ ユーザーフォルダーに範囲を限定することを検討してください:
users/${userId}/${filename}
署名付き URL のドメイン要件
重大な注意:署名付き URL が機能するのは S3 API ドメインのみで、カスタムドメインでは機能しません。
// ❌ WRONG - Presigned URLs don't work with custom domains
const url = new URL(`https://cdn.example.com/${filename}`);
const signed = await r2Client.sign(
new Request(url, { method: 'PUT' }),
{ aws: { signQuery: true } }
);
// This URL will fail - presigning requires S3 domain
// ✅ CORRECT - Use R2 storage domain for presigned URLs
const url = new URL(
`https://${accountId}.r2.cloudflarestorage.com/${filename}`
);
const signed = await r2Client.sign(
new Request(url, { method: 'PUT' }),
{ aws: { signQuery: true } }
);
// Pattern: Upload via presigned S3 URL, serve via custom domain
async function generateUploadUrl(filename: string) {
const uploadUrl = new URL(
`https://${accountId}.r2.cloudflarestorage.com/${filename}`
);
const signed = await r2Client.sign(
new Request(uploadUrl, { method: 'PUT' }),
{ aws: { signQuery: true } }
);
return {
uploadUrl: signed.url, // For client upload (S3 domain)
publicUrl: `https://cdn.example.com/${filename}` // For serving (custom domain)
};
}
出典:コミュニティの知見
Wrangler の API トークン要件
⚠️ Wrangler CLI には「Admin Read & Write」権限が必要です。「Object Read & Write」ではありません。
Wrangler の操作用に API トークンを作成する場合:
- ✅ 使用する:R2 → Admin Read & Write
- ❌ 使用しない:R2 → Object Read & Write(403 Forbidden エラーが発生します)
理由:「Object Read & Write」は S3 API からの直接アクセス専用です。バケット操作には、Wrangler の管理者レベルの権限が必要です。
# With wrong permissions:
export CLOUDFLARE_API_TOKEN="token_with_object_readwrite"
wrangler r2 object put my-bucket/file.txt --file=./file.txt --remote
# ✘ [ERROR] Failed to fetch - 403: Forbidden
# With correct permissions (Admin Read & Write):
wrangler r2 object put my-bucket/file.txt --file=./file.txt --remote
# ✔ Success
CORS の設定
ブラウザーからアクセスする前に、バケット設定(Dashboard → R2 → Bucket → Settings → CORS Policy)で CORS を設定してください。
Dashboard 形式と CLI 形式
⚠️ wrangler CLI と Dashboard UI では、異なる CORS 形式が使用されます。これはよく混乱の原因になります。
Dashboard 形式(UI のみで使用できます):
[{
"AllowedOrigins": ["https://example.com"],
"AllowedMethods": ["GET", "PUT"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}]
CLI 形式(wrangler r2 bucket cors で必要です):
{
"rules": [{
"allowed": {
"origins": ["https://www.example.com"],
"methods": ["GET", "PUT"],
"headers": ["Content-Type", "Authorization"]
},
"exposeHeaders": ["ETag", "Content-Length"],
"maxAgeSeconds": 8640
}]
}
# Using CLI format
wrangler r2 bucket cors set my-bucket --file cors-config.json
# Error if using Dashboard format:
# "The CORS configuration file must contain a 'rules' array"
カスタムドメインの CORS
R2 でカスタムドメインを使用する場合、CORS は2 つのレイヤーで処理されます:
- R2 バケットの CORS:すべてのアクセス方法(署名付き URL、S3 への直接アクセス)に適用されます
- Transform Rules の CORS:カスタムドメインの Cloudflare Cache 設定を通じて、追加の CORS ヘッダーを設定します
// Bucket CORS (set via dashboard or wrangler)
{
"rules": [{
"allowed": {
"origins": ["https://app.example.com"],
"methods": ["GET", "PUT"],
"headers": ["Content-Type"]
},
"maxAgeSeconds": 3600
}]
}
// Additional CORS via Transform Rules (Dashboard → Rules → Transform Rules)
// Modify Response Header: Access-Control-Allow-Origin: https://app.example.com
// Order of CORS evaluation:
// 1. R2 bucket CORS (if presigned URL or direct R2 access)
// 2. Transform Rules CORS (if via custom domain)
出典:コミュニティの知見
署名付き URL の場合:CORS は R2 が直接処理します(Worker ではなく、バケットで設定してください)。
HTTP メタデータとカスタムメタデータ
// HTTP metadata (standard headers)
await env.MY_BUCKET.put('file.pdf', data, {
httpMetadata: {
contentType: 'application/pdf',
cacheControl: 'public, max-age=31536000, immutable',
contentDisposition: 'attachment; filename="report.pdf"',
contentEncoding: 'gzip',
},
customMetadata: {
userId: '12345',
version: '1.0',
} // Max 2KB total, keys/values must be strings
});
// Read metadata
const object = await env.MY_BUCKET.head('file.pdf');
console.log(object.httpMetadata, object.customMetadata);
エラー処理
よくある R2 のエラー
try {
await env.MY_BUCKET.put(key, data);
} catch (error: any) {
const message = error.message;
if (message.includes('R2_ERROR')) {
// Generic R2 error
} else if (message.includes('exceeded')) {
// Quota exceeded
} else if (message.includes('precondition')) {
// Conditional operation failed
} else if (message.includes('multipart')) {
// Multipart upload error
}
console.error('R2 Error:', message);
return c.json({ error: 'Storage operation failed' }, 500);
}
リトライロジック
R2では運用上の問題により、2025年第1四半期に大規模な障害が2回発生しました(2月6日:59分間、3月21日:1時間7分間)。プラットフォームエラーに対して、指数バックオフを用いた堅牢なリトライロジックを実装してください。
async function r2WithRetry<T>(
operation: () => Promise<T>,
maxRetries = 5
): Promise<T> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await operation();
} catch (error: any) {
const message = error.message;
// Retry on transient errors and platform issues
const is5xxError =
message.includes('500') ||
message.includes('502') ||
message.includes('503') ||
message.includes('504');
const isRetryable =
is5xxError ||
message.includes('network') ||
message.includes('timeout') ||
message.includes('temporarily unavailable');
if (!isRetryable || attempt === maxRetries - 1) {
throw error;
}
// Exponential backoff (longer for platform errors)
// 5xx errors: 1s, 2s, 4s, 8s, 16s (up to 31s total)
// Other errors: 1s, 2s, 4s, 5s, 5s (up to 17s total)
const delay = is5xxError
? Math.min(1000 * Math.pow(2, attempt), 16000)
: Math.min(1000 * Math.pow(2, attempt), 5000);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw new Error('Max retries exceeded');
}
// Usage
const object = await r2WithRetry(() =>
env.MY_BUCKET.get('important-file.txt')
);
プラットフォームの信頼性:R2は一般的に信頼性の高いサービスですが、2025年第1四半期の障害は、本番アプリケーションにおけるリトライロジックの重要性を示しています。すべての5xxエラーは、指数バックオフを用いて再試行してください。
情報源:
パフォーマンスの最適化
// Batch delete (up to 1000 keys)
await env.MY_BUCKET.delete(['file1.txt', 'file2.txt', 'file3.txt']);
// Range requests for large files
const partial = await env.MY_BUCKET.get('video.mp4', {
range: { offset: 0, length: 10 * 1024 * 1024 } // First 10MB
});
// Cache headers for immutable assets
await env.MY_BUCKET.put('static/app.abc123.js', jsData, {
httpMetadata: { cacheControl: 'public, max-age=31536000, immutable' }
});
// Checksums for data integrity
const md5Hash = await crypto.subtle.digest('MD5', fileData);
await env.MY_BUCKET.put('important.dat', fileData, { md5: md5Hash });
同時書き込みのレート制限
⚠️ 同じオブジェクトキーへの高頻度な同時書き込みは、HTTP 429のレート制限を引き起こします。
// ❌ BAD: Multiple Workers writing to same key rapidly
async function logToSharedFile(env: Env, logEntry: string) {
const existing = await env.LOGS.get('global-log.txt');
const content = (await existing?.text()) || '';
await env.LOGS.put('global-log.txt', content + logEntry);
// High write frequency to same key = 429 errors
}
// ✅ GOOD: Shard by timestamp or ID (distribute writes)
async function logWithSharding(env: Env, logEntry: string) {
const timestamp = Date.now();
const shard = Math.floor(timestamp / 60000); // 1-minute shards
await env.LOGS.put(`logs/${shard}.txt`, logEntry, {
customMetadata: { timestamp: timestamp.toString() }
});
// Different keys = no rate limiting
}
// ✅ ALTERNATIVE: Use Durable Objects for append operations
// Durable Objects can handle high-frequency updates to same state
// ✅ ALTERNATIVE: Use Queues + batch processing
// Buffer writes and batch them with unique keys
情報源:R2の制限に関するドキュメント
R2.devドメインのレート制限
🚨 重大:{bucket}.{account}.r2.cloudflarestorage.com(r2.dev)ドメインは、本番環境での使用を想定していません。
r2.devの制限:
- ❌ レート制限は変動します(毎秒数百リクエスト程度から開始)
- ❌ 帯域幅が制限されます
- ❌ SLAやパフォーマンスの保証はありません
- ❌ 負荷がかかると429 Too Many Requestsが返されます
本番環境では、必ずカスタムドメインを使用してください
// ❌ NOT for production - r2.dev endpoint
const publicUrl = `https://${bucketName}.${accountId}.r2.cloudflarestorage.com/${key}`;
// This will be rate limited in production
// ✅ Production: Custom domain
const productionUrl = `https://cdn.example.com/${key}`;
// Setup custom domain:
// 1. Dashboard → R2 → Bucket → Settings → Custom Domains
// 2. Add your domain (e.g., cdn.example.com)
// 3. Benefits:
// - No rate limiting beyond account limits
// - Cloudflare Cache support
// - Custom cache rules via Workers
// - Full CDN features
r2.devはテスト/開発専用です。本番環境ではカスタムドメインが必須です。
情報源:R2の制限に関するドキュメント
ベストプラクティスのまとめ
必ず行うこと:
- すべてのアップロードで
contentTypeを設定する - 複数のオブジェクトを削除する場合は、バッチ削除を使用する(最大1000個)
- 静的アセットにはキャッシュヘッダーを設定する
- クライアントから大きなファイルをアップロードする場合は、署名付きURLを使用する(S3ドメインのみ)
- 100MBを超えるファイルにはマルチパートを使用する
- ブラウザーからのアップロード前にCORSを設定する(wranglerではCLI形式を使用する)
- 署名付きURLに有効期限を設定する(1~24時間)
- メタデータのみが必要な場合は
head()を使用する - 上書きを防ぐため、条件付き操作を使用する
- 本番環境ではカスタムドメインを使用する(r2.devは使用しない)
- レート制限を避けるため、書き込みを複数のキーに分散する
- メタデータを取得するには、
list()でincludeパラメーターを使用する - 5xxエラーに対して、指数バックオフを用いたリトライロジックを実装する
決して行わないこと:
- R2のアクセスキーをクライアント側のコードに公開しない
contentTypeの設定を省略しない(ファイルがバイナリとしてダウンロードされます)- ループ内で削除しない(バッチ削除を使用してください)
- ブラウザーからのアップロードでCORSの設定を省略しない
- 小さなファイル(5MB未満)にマルチパートを使用しない
- 1回の呼び出しで1000を超えるキーを削除しない
- 署名付きURLの有効期限設定を省略しない(セキュリティリスクになります)
- 本番環境でr2.devドメインを使用しない(レート制限があります)
- カスタムドメインで署名付きURLを使用しない(S3ドメインを使用してください)
- 同じキーに高頻度で書き込まない(429エラーの原因になります)
- wranglerで「Object Read & Write」トークンを使用しない(「Admin Read & Write」を使用してください)
マルチテナントアーキテクチャ
バケットの上限がアカウントあたり100万バケットに引き上げられたことで、大規模アプリケーションでもテナントごとのバケットを利用できるようになりました。
// Option 1: Per-tenant buckets (now scalable to 1M tenants)
const bucketName = `tenant-${tenantId}`;
const bucket = env[bucketName]; // Dynamic binding
// Option 2: Key prefixing (still preferred for most use cases)
await env.MY_BUCKET.put(`tenants/${tenantId}/file.txt`, data);
// Choose based on:
// - Per-tenant buckets: Strong isolation, separate billing/quotas
// - Key prefixing: Simpler, fewer resources, easier to manage
情報源:R2の制限に関するドキュメント
防止される既知の問題
このスキルは、文書化されている 13 件の問題を防止します。
| Issue # | 問題 | エラー | 防止策 |
|---|---|---|---|
| #1 | ブラウザーでの CORS エラー | ブラウザーでアップロード/ダウンロードできない | バケット設定で CORS を構成し、正しい CLI 形式を使用します |
| #2 | ファイルがバイナリとしてダウンロードされる | content-type が設定されていない | アップロード時に必ず httpMetadata.contentType を設定します |
| #3 | 署名付き URL の有効期限 | URL が期限切れにならない | 必ず X-Amz-Expires を設定します(1~24 時間) |
| #4 | マルチパートアップロードの制限 | パートが上限を超える | パートは 5MB~100MB にし、最大 10,000 パートにします |
| #5 | 一括削除の制限 | 1000 件を超えるキーの削除に失敗する | 削除対象を 1000 件ずつのバッチに分割します |
| #6 | カスタムメタデータの超過 | 2KB の上限を超える | カスタムメタデータを 2KB 未満に抑えます |
| #7 | list() でメタデータが欠落する | httpMetadata が undefined になる | include: ['httpMetadata', 'customMetadata'] パラメーターを使用します(Issue #10870) |
| #8 | CORS 形式の混同 | 「'rules' 配列を含める必要があります」 | wrangler では rules ラッパーを含む CLI 形式を使用します(Issue #10076) |
| #9 | API トークンの 403 エラー | 「取得に失敗しました - 403」 | wrangler では「Object Read & Write」ではなく「Admin Read & Write」を使用します(Issue #9235) |
| #10 | r2.dev のレート制限 | 本番環境で HTTP 429 が発生する | カスタムドメインを使用し、本番環境では r2.dev を決して使用しません(R2 Limits) |
| #11 | 同時書き込みによる 429 エラー | 同じキーに頻繁に書き込む | 書き込みを異なるキーに分散します(R2 Limits) |
| #12 | 署名付き URL のドメインエラー | 署名付き URL が失敗する | カスタムドメインではなく、S3 ドメインのみを使用します(Community) |
| #13 | プラットフォームの停止 | 停止中に 5xx エラーが発生する | 指数バックオフを使用したリトライロジックを実装します(Feb 6、Mar 21) |
開発のベストプラクティス
ローカル R2 ストレージのクリーンアップ
⚠️ ローカル R2 の DELETE 操作では、blob ファイルがクリーンアップされません。wrangler dev の使用時に削除したオブジェクトは .wrangler/state/v3/r2/{bucket-name}/blobs/ に残り、ローカルストレージが際限なく増加します。
# Symptom: .wrangler/state grows large during development
du -sh .wrangler/state/v3/r2/
# Fix: Manually cleanup local R2 storage
rm -rf .wrangler/state/v3/r2/
# Alternative: Use remote R2 for development
wrangler dev --remote
リモート R2 アクセスの問題
⚠️ --remote を使ったローカル開発では、.get() 操作が不安定になることがあります。put() は正しく動作するにもかかわらず、get() が undefined を返すという報告があります。
# If experiencing issues with remote R2 in local dev:
# Option 1: Use local buckets instead (recommended)
wrangler dev # No --remote flag
# Option 2: Deploy to preview environment for testing
wrangler deploy --env preview
# Option 3: Add retry logic if must use --remote
async function safeGet(bucket: R2Bucket, key: string) {
for (let i = 0; i < 3; i++) {
const obj = await bucket.get(key);
if (obj && obj.body) return obj;
await new Promise(r => setTimeout(r, 1000));
}
throw new Error('Failed to get object after retries');
}
出典:GitHub Issue #8868(コミュニティからの情報)
Wrangler コマンドのリファレンス
# Bucket management
wrangler r2 bucket create <BUCKET_NAME>
wrangler r2 bucket list
wrangler r2 bucket delete <BUCKET_NAME>
# Object management
wrangler r2 object put <BUCKET_NAME>/<KEY> --file=<FILE_PATH>
wrangler r2 object get <BUCKET_NAME>/<KEY> --file=<OUTPUT_PATH>
wrangler r2 object delete <BUCKET_NAME>/<KEY>
# List objects
wrangler r2 object list <BUCKET_NAME>
wrangler r2 object list <BUCKET_NAME> --prefix="folder/"
公式ドキュメント
- R2 の概要:https://developers.cloudflare.com/r2/
- はじめに:https://developers.cloudflare.com/r2/get-started/
- Workers API:https://developers.cloudflare.com/r2/api/workers/workers-api-reference/
- マルチパートアップロード:https://developers.cloudflare.com/r2/api/workers/workers-multipart-usage/
- 署名付き URL:https://developers.cloudflare.com/r2/api/s3/presigned-urls/
- CORS の構成:https://developers.cloudflare.com/r2/buckets/cors/
- パブリックバケット:https://developers.cloudflare.com/r2/buckets/public-buckets/
R2 への保存を始めましょう! 🚀
最終確認日:2026-01-20 | スキルのバージョン:2.0.0 | 変更点:コミュニティ調査で判明した既知の問題を 7 件追加しました(list() のメタデータ、CORS 形式の混同、API トークンの権限、r2.dev のレート制限、同時書き込みの制限、署名付き URL のドメイン要件、プラットフォーム停止時のリトライパターン)。5xx エラーに対するリトライロジックを強化し、開発のベストプラクティスのセクションを追加し、バケットの上限を 1M に引き上げたことを記載しました。