Skip to content

Cloudflare R2: An S3 Alternative with Zero Egress Fees

Mar 27, 2026 1 min
TL;DR R2 is Cloudflare's object storage service — S3-compatible API, zero egress fees, and native Workers binding. Stop worrying about bandwidth bills for media-heavy applications.
Table of Contents
  1. What is R2
  2. Basic Usage (Cloudflare Workers)
  3. Accessing R2 via the S3-Compatible API
  4. How NobodyClimb Uses R2
  5. Trade-offs
  6. When to Choose R2
  7. Changelog
  8. References

🌏 中文版

If your app runs on Cloudflare Workers and you need somewhere to store images, S3 works — but you have to manage an AWS account, configure CORS, and keep an eye on egress fees (AWS charges for data transferred out of S3, and those bills can get nasty at scale). R2 solves all of this: S3-compatible API, zero egress fees, running on the Cloudflare network.

What is R2

R2 is Cloudflare's object storage service, designed as a drop-in replacement for S3. Within the Cloudflare Workers ecosystem, R2 is the most natural storage choice.

Key differences from S3

AWS S3Cloudflare R2
API compatibilityNativeS3-compatible (drop-in replacement)
Egress (transfer to the Internet)Billed per GBFree
CDN integrationRequires separate CloudFront setupDirect Cloudflare CDN
Workers integrationRequires SDK + added latencyNative binding, low latency
Region choiceMany regionsAutomatic placement, plus location hints and jurisdictional restrictions

Unit prices move — check R2 Pricing and AWS's own page. What stays true is the shape: R2 bills on three axes — storage (GB-month), Class A operations (state-mutating ones like PutObject and ListObjects), and Class B operations (reads like GetObject and HeadObject). Deletes are free operations. Egress is never charged.

Zero egress fees matter a lot for media-heavy applications. Climbing logs involve large numbers of photos and video thumbnails — if those lived in S3, every time someone opened a photo you'd be paying egress. R2 eliminates that cost.

One trap that is easy to underestimate: Class A operations cost an order of magnitude more than Class B. Uploading large numbers of small files, or paging through a bucket with frequent ListObjects calls, produces a bill dominated by operations rather than storage. The Infrequent Access storage class is also not a free win — on top of pricier operations it adds a data retrieval fee and a 30-day minimum storage duration, so cold data does not automatically get cheaper by moving there.

Basic Usage (Cloudflare Workers)

Wrangler configuration binding

{
  "r2_buckets": [{ "binding": "BUCKET", "bucket_name": "nobodyclimb-media" }]
}

Working with R2 inside a Worker

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', // cache images for 1 year
        },
      });
    }

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

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

Accessing R2 via the S3-Compatible API

R2 also supports the AWS SDK, making it easy to upload from a Next.js server action or any other external service:

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);

How NobodyClimb Uses R2

NobodyClimb runs on a fully Cloudflare-native stack, with R2 handling all media storage:

User uploads a photo (climb log, story cover image)


Hono API (auth + generate upload key)


      R2 Bucket
    nobodyclimb-media/
    ├── climbs/{climbId}/
    │   ├── photo-original.jpg
    │   └── photo-thumb.jpg      ← thumbnail, video preview
    └── stories/{storyId}/
        └── cover.jpg


Cloudflare CDN (images served with cache-control, cached globally)

Both the original image and a thumbnail are stored separately. Thumbnails are what get displayed during page load — they need to be fast. The original only loads when someone actually opens it, so a bit more latency is acceptable.

Trade-offs

Pros

  • Zero egress fees — significant savings for media-heavy applications
  • S3-compatible, so migration costs are low
  • Native Workers binding with low latency
  • Automatic Cloudflare CDN integration

Cons

  • Less mature ecosystem than S3 — a decade-plus of third-party tooling, audit and compliance integrations is not caught up quickly
  • Fewer selectable physical locations than S3 has regions (R2 places buckets automatically by default; location hints and jurisdictional restrictions exist but with different granularity)
  • Large organizations may still need the deep integration that the AWS ecosystem provides

Two things are no longer cons: object lifecycle rules (auto-expiry, auto-transition to Infrequent Access) and event notifications (fire a Queue or Worker on object create/delete — the equivalent of S3's Lambda triggers) both exist now.

When to Choose R2

  • You've already committed to Cloudflare Workers as your compute platform
  • Your app is media-heavy (images, video thumbnails) and egress costs are a concern
  • You're at a medium scale and don't need enterprise-grade AWS features

If you're not using Workers, S3 is probably the better fit. R2's biggest value comes from seamless integration with the Cloudflare ecosystem — using it in isolation reduces the advantage considerably.

Changelog

  • 2026-08-19: Fact-checked against primary sources and refreshed; perishable details handed back to official docs. Added to the "Cloudflare Edge Stack" series.

References