※本記事には広告(A8・楽天アフィリエイト)リンクが含まれる場合があります。

「AWS S3で画像配信したら月の転送料が想定の3倍になった」「個人開発でオブジェクトストレージを使いたいけど、転送費用が怖くて踏み出せない」——こういう悩みを持つ個人開発者は多いはずです。

その解決策が Cloudflare R2 です。AWS S3と互換性を持ちながら、エグレス(データ転送出力)料金が無料というインパクトのある特徴を持つオブジェクトストレージです。2022年にGA(正式提供)となって以来、個人開発者・スタートアップの間で急速に普及し、2026年現在では「画像・ファイルの保存はR2で決まり」という空気になりつつあります。

この記事では、R2の基本概念からバケット作成・Cloudflare Workersとの連携・署名付きURLによるファイルアップロード・カスタムドメインの設定まで、個人開発に必要な知識を実践コード付きで徹底解説します。

Cloudflare R2とは?AWS S3との違いを整理する

R2はCloudflareが提供するS3互換のオブジェクトストレージです。「S3互換」とは、AWS S3のAPI仕様に準拠しているため、既存のS3用ライブラリ(AWS SDK・boto3・rcloneなど)がほぼそのまま使えることを意味します。既存コードの移行コストがほぼゼロなのも魅力の一つです。

R2とAWS S3の料金比較(2026年4月時点)

項目 Cloudflare R2 AWS S3(東京リージョン)
ストレージ 10GB/月まで無料、超過分$0.015/GB $0.025/GB
Aクラス操作(PUT/POST等) 100万回/月まで無料、超過$4.50/百万回 $0.0047/千回
Bクラス操作(GET等) 1000万回/月まで無料、超過$0.36/百万回 $0.00037/千回
エグレス(転送量) 無料 $0.114/GB(最初の10TB)
Workers連携 ✅ ネイティブ対応 Lambda経由で別途設定

圧倒的な差はエグレス料金です。S3では静的ファイルを配信するたびにGB単位で課金が発生しますが、R2は完全無料。月間数GBのトラフィックがある画像配信サービスでは、この差だけで数百ドルの節約になります。

R2が特に向いているユースケース

  • Webアプリへのユーザーアップロード画像・動画の保存
  • 静的アセット(CSS/JS/フォントなど)のグローバルCDN配信
  • バックアップ・ログファイルの長期ストレージ
  • Workers経由での画像リサイズ・加工パイプライン
  • マイクロSaaSでの添付ファイル管理

逆に、リージョン指定が法的に必要な場合(GDPR対応でEU縛りが必要なケースを除く)や、細かいACL制御が必要な場面では別の選択肢も検討してください。

R2バケットを作成する(ダッシュボード編)

前提条件

  • Cloudflareアカウント(無料)
  • クレジットカードの登録(R2有効化に必要。無料枠内なら請求なし)
  • wrangler 3以上(CLIから操作する場合)

ダッシュボードからバケットを作る

Cloudflareダッシュボードにログインし、左サイドバーから「R2 Storage」を選択します。初回はクレジットカード登録を求められますが、無料枠(10GB/月、操作100万回/月)を超えない限り請求は発生しません。

  1. 「バケットを作成」ボタンをクリック
  2. バケット名を入力(小文字英数字とハイフンのみ。ドットは使用不可)
  3. リージョンヒントを選択(「APAC」を選ぶと日本に近いエッジに優先配置)
  4. 「作成」をクリック

バケットが作成されたら、設定画面の「S3 API」欄にエンドポイントURLが表示されます(https://<アカウントID>.r2.cloudflarestorage.com 形式)。このURLと後述のAPIトークンがプログラムから接続するための鍵です。

APIトークンを発行する

R2のダッシュボード右上「R2 APIトークンを管理」→「APIトークンを作成」から発行します。

  • 権限:「オブジェクトの読み取りと書き込み」(読み取りのみなら「読み取り」を選択)
  • バケットのスコープ:特定のバケットに絞ることを推奨
  • TTL:長期利用なら「無期限」も可

発行後に表示される「アクセスキーID」と「シークレットアクセスキー」は一度しか表示されないので必ず控えてください

広告・楽天アフィリエイト

開発者向けガジェット

PC周辺機器を楽天市場でチェック。

楽天ポイント還元

楽天市場を見る →

広告・楽天アフィリエイト

メカニカルキーボード

打鍵感で選ぶ開発者向けキーボード。

楽天ポイント還元

キーボードを楽天で見る →

wranglerでR2をセットアップする(CLI編)

wrangler(Cloudflareの公式CLI)を使えば、ターミナルだけでバケット作成からデプロイまで完結します。

# wranglerのインストール
npm install -g wrangler

# Cloudflareアカウントにログイン
wrangler login

# R2バケットを作成
wrangler r2 bucket create my-app-assets

# バケット一覧を確認
wrangler r2 bucket list

# ファイルをアップロード(テスト用)
wrangler r2 object put my-app-assets/test.txt --file ./test.txt

# オブジェクト一覧を確認
wrangler r2 object list my-app-assets

Cloudflare Workersから R2を操作する

R2の真価はCloudflare Workersとのシームレスな連携にあります。Hono.js + Workersと組み合わせれば、ファイルのアップロード・取得・削除を処理するAPIサーバーをエッジで動かせます。

wrangler.tomlにR2バインディングを追記

name = "my-app"
main = "src/index.ts"
compatibility_date = "2026-01-01"

[[r2_buckets]]
binding = "BUCKET"
bucket_name = "my-app-assets"

WorkerからR2オブジェクトを操作する(Hono.js例)

import { Hono } from 'hono';

type Env = {
  BUCKET: R2Bucket;
};

const app = new Hono<{ Bindings: Env }>();

// ファイルをアップロード(multipart/form-data)
app.post('/upload', async (c) => {
  const body = await c.req.parseBody();
  const file = body['file'] as File;

  if (!file) {
    return c.json({ error: 'ファイルが見つかりません' }, 400);
  }

  const key = `uploads/${Date.now()}-${file.name}`;
  const arrayBuffer = await file.arrayBuffer();

  await c.env.BUCKET.put(key, arrayBuffer, {
    httpMetadata: {
      contentType: file.type,
    },
  });

  return c.json({ key, message: 'アップロード完了' });
});

// ファイルを取得して配信
app.get('/files/:key{.+}', async (c) => {
  const key = c.req.param('key');
  const object = await c.env.BUCKET.get(key);

  if (!object) {
    return c.json({ error: 'ファイルが見つかりません' }, 404);
  }

  const headers = new Headers();
  object.writeHttpMetadata(headers);
  headers.set('etag', object.httpEtag);

  return new Response(object.body, { headers });
});

// ファイルを削除
app.delete('/files/:key{.+}', async (c) => {
  const key = c.req.param('key');
  await c.env.BUCKET.delete(key);
  return c.json({ message: '削除完了' });
});

// バケット内のファイル一覧
app.get('/files', async (c) => {
  const list = await c.env.BUCKET.list({ prefix: 'uploads/' });
  return c.json({
    objects: list.objects.map(obj => ({
      key: obj.key,
      size: obj.size,
      uploaded: obj.uploaded,
    })),
  });
});

export default app;

Workers内から c.env.BUCKET.put() / .get() / .delete() を呼ぶだけで操作できます。ネットワーク設定もSDKインポートも不要。これがWorkersネイティブな開発の快適さです。

署名付きURL(Presigned URL)でクライアントから直接アップロードする

画像や動画などの大きなファイルをWorkersサーバー経由で転送すると、Workersの実行時間制限(CPU時間)に引っかかる場合があります。そこで推奨されるのが「署名付きURL」パターンです。

サーバー側で期限付きの署名URLを発行し、クライアントがそのURLに直接ファイルをアップロードします。これによりWorkersへの負荷を最小化できます。

// 署名付きURL(Presigned URL)を発行するWorkerのエンドポイント
import { AwsClient } from 'aws4fetch'; // npm install aws4fetch

app.get('/presigned-url', async (c) => {
  const filename = c.req.query('filename');
  const contentType = c.req.query('contentType') || 'application/octet-stream';

  if (!filename) {
    return c.json({ error: 'filenameが必要です' }, 400);
  }

  const key = `uploads/${Date.now()}-${filename}`;
  const accountId = c.env.CF_ACCOUNT_ID;
  const bucketName = c.env.BUCKET_NAME;

  const client = new AwsClient({
    accessKeyId: c.env.R2_ACCESS_KEY_ID,
    secretAccessKey: c.env.R2_SECRET_ACCESS_KEY,
    region: 'auto',
    service: 's3',
  });

  const url = new URL(
    `https://${accountId}.r2.cloudflarestorage.com/${bucketName}/${key}`
  );
  url.searchParams.set('X-Amz-Expires', '3600'); // 1時間有効

  const signed = await client.sign(
    new Request(url.toString(), { method: 'PUT' }),
    { aws: { signQuery: true } }
  );

  return c.json({
    uploadUrl: signed.url,
    key,
    expiresIn: 3600,
  });
});

クライアント側はこのURLに対してPUTリクエストを送るだけでアップロードが完了します。Workersを介さないため高速で、大容量ファイルにも対応しやすい設計です。

wrangler.tomlにシークレットを設定

# シークレットはwrangler secretコマンドで安全に設定
wrangler secret put R2_ACCESS_KEY_ID
wrangler secret put R2_SECRET_ACCESS_KEY
wrangler secret put CF_ACCOUNT_ID
wrangler secret put BUCKET_NAME

カスタムドメインでR2バケットをCDN配信する

R2には「パブリックバケット」機能があり、バケット内のオブジェクトをHTTPSで公開できます。2つの方法があります。

方法1:r2.devサブドメイン(開発・テスト向け)

バケットの設定 → 「パブリックアクセス」→「r2.devサブドメイン」を有効化すると、pub-xxxxx.r2.dev/オブジェクトキー でアクセスできます。ただし本番環境での利用は推奨されていません(レート制限あり)。

方法2:カスタムドメイン(本番推奨)

Cloudflareのネームサーバーで管理しているドメインであれば、バケットにカスタムドメインを紐付けられます。

  1. バケットの設定 → 「カスタムドメイン」→「カスタムドメインを接続」
  2. 使用するサブドメインを入力(例:assets.example.com
  3. Cloudflare DNSにCNAMEレコードが自動追加されます
  4. 数分でHTTPSが有効になります

これにより、https://assets.example.com/uploads/photo.jpg というURLでオブジェクトにアクセスできます。Cloudflareのグローバルネットワーク(300以上のPOP)がCDNとして機能するため、世界中から高速に配信可能です。

広告・楽天アフィリエイト

PCモニター

デュアルディスプレイや高解像度モデルをチェック。

楽天ポイント還元

モニターを楽天で見る →

広告・楽天アフィリエイト

プログラミング技術書

実践寄りの技術書・参考書を探す。

楽天ポイント還元

技術書を楽天で見る →

S3互換APIでR2を使う(既存コードの移行)

既にAWS SDK(JavaScript v3)を使っているプロジェクトなら、エンドポイントURLを変えるだけで移行できます。

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

// R2向けS3クライアントの設定
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!,
  },
});

// ファイルをアップロード
async function uploadFile(key: string, body: Buffer, contentType: string) {
  await r2.send(new PutObjectCommand({
    Bucket: process.env.BUCKET_NAME,
    Key: key,
    Body: body,
    ContentType: contentType,
  }));
}

// 署名付きURLを発行(1時間有効)
async function getPresignedUrl(key: string): Promise<string> {
  return getSignedUrl(r2, new GetObjectCommand({
    Bucket: process.env.BUCKET_NAME,
    Key: key,
  }), { expiresIn: 3600 });
}

S3→R2移行のポイントは3点です。

  • region'auto' に設定
  • endpoint をR2のエンドポイントに変更
  • IAMの代わりにR2 APIトークンを使用

R2の実践的な活用パターン:マイクロSaaSでの画像管理

例えば、ユーザーがプロフィール画像をアップロードできるWebアプリを構築する場合の全体フローです。

// フロントエンド(React/Vueなど)での実装例
async function uploadProfileImage(file: File) {
  // Step 1: サーバーから署名付きURLを取得
  const res = await fetch(`/api/presigned-url?filename=${file.name}&contentType=${file.type}`);
  const { uploadUrl, key } = await res.json();

  // Step 2: R2に直接アップロード(WorkersサーバーをBYPASS)
  await fetch(uploadUrl, {
    method: 'PUT',
    body: file,
    headers: { 'Content-Type': file.type },
  });

  // Step 3: アップロード完了をサーバーに通知してDBに保存
  await fetch('/api/profile/avatar', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ key }),
  });
}

このパターンを使えば、Cloudflare D1(SQLiteデータベース)にメタデータ(ユーザーID・ファイルキー・アップロード日時)を保存し、R2に実体ファイルを保存するという役割分担ができます。Workers + D1 + R2 の三点セットで、サーバーレスなフルスタックアーキテクチャが月$0〜で成立します。

セキュリティ上の注意点

バケットを不用意に公開しない

デフォルトでR2バケットはプライベートです。「パブリックアクセス」を有効化する場合は、本当に全世界に公開して問題ないコンテンツのみにしてください。ユーザーの個人データやプライベートなファイルは署名付きURLで配信するのが原則です。

APIトークンのスコープを絞る

APIトークンは必要最小限の権限に絞りましょう。読み取り専用でいい場合は「読み取りのみ」に設定し、バケットのスコープも特定のバケットに限定することを推奨します。

CORSを適切に設定する

クライアントから直接R2にアップロードする場合、CORSの設定が必要です。ワイルドカード(*)は開発中のみ使用し、本番では許可するオリジンを明示的に指定してください。

[
  {
    "AllowedOrigins": ["https://your-app.pages.dev"],
    "AllowedMethods": ["GET", "PUT", "POST"],
    "AllowedHeaders": ["Content-Type"],
    "MaxAgeSeconds": 3600
  }
]

CORS設定はwrangler CLIまたはR2のS3互換APIから設定できます。

他のオブジェクトストレージとの比較

サービス エグレス料金 ストレージ単価 Workers連携 無料枠
Cloudflare R2 ✅ 無料 $0.015/GB ✅ ネイティブ 10GB/月
AWS S3(東京) $0.114/GB $0.025/GB Lambda経由 5GB(12ヶ月限定)
Backblaze B2 $0.01/GB(Cloudflare提携で無料) $0.006/GB 10GB
Supabase Storage $0.09/GB $0.021/GB △(Edge Functions) 1GB
Vercel Blob $0.10/GB $0.023/GB △(Vercel Functions) なし

純粋なストレージ単価ではBackblaze B2の方が安いですが、Cloudflare連携を考慮するとエグレス無料になります。Workersとのネイティブ連携・シームレスなCDN機能・管理の一元化を考えると、Cloudflareで完結させる場合はR2が最強の選択肢です。

よくある質問と答え

Q: R2に保存したファイルはどのくらい安全?

Cloudflareはエンタープライズ向けサービスも展開しており、信頼性は高いです。ただし、重要なデータは定期バックアップ(別バケットや別サービスへのコピー)を設定することを推奨します。

Q: 無料枠を超えたらどうなる?

自動的に有料プランに移行し、超過分のみ課金されます。予期せぬ高額課金を防ぐため、Cloudflareのアラート設定(使用量の閾値通知)を有効にしておくことをおすすめします。

Q: 既存のS3バケットからR2に移行できる?

rcloneを使えばS3→R2へのデータ移行が可能です。rclone copy s3:my-bucket r2:my-r2-bucket のようなコマンドで一括コピーできます。コードはエンドポイントURLの変更のみで対応できます。

Q: R2でDurableObjectsとの連携は?

可能です。Durable ObjectsからR2にアクセスするユースケースとして、リアルタイムコラボレーションアプリでのファイル共有などが挙げられます。Workers・D1・R2・Durable Objectsを組み合わせることで、複雑なアーキテクチャもCloudflare上で完結できます。

広告・楽天アフィリエイト

ガジェット・オーディオ

イヤホン・便利ガジェットをポイント還元で。

楽天ポイント還元

ガジェットを楽天で見る →

まとめ:個人開発者がR2を選ぶべき理由

Cloudflare R2は「エグレス料金ゼロ」という一点だけでも個人開発者にとって革命的なサービスです。加えてS3互換APIによる移行の容易さ・Workersとのネイティブ連携・無料10GB枠・グローバルCDNが標準装備と、コストパフォーマンスは圧倒的です。

Cloudflare WorkersでAPIサーバーを立て、D1でデータ管理、R2でファイル管理、Cloudflare Pagesでフロントエンド配信——この組み合わせなら月$0〜でフルスタックなマイクロSaaSが運用できます。

Webサービスの監視も忘れずに。StatusCraft(ステータスページツール)でサービスの稼働状況をユーザーに見せると、信頼度が上がります。また、Webサイトの変化をリアルタイムで検知したい場合は PagePulse(Web監視ツール) も活用してみてください。

まずは無料枠でR2バケットを作り、ローカルで動くサンプルコードを試してみましょう。Cloudflareエコシステムの快適さに一度触れると、もう戻れなくなります。

広告・楽天アフィリエイト

USBハブ・周辺機器

ドッキング・ケーブル類をまとめて比較。

楽天ポイント還元

周辺機器を楽天で見る →

🚀 Cloudflare R2を今すぐ試す

Cloudflareアカウントは無料で作成でき、R2の無料枠(10GB/月)もすぐに使えます。個人開発のオブジェクトストレージをR2に切り替えて、エグレス料金ゼロの快適さを体験してください。

Cloudflare R2を始める(無料)