AV
HomeAboutProjectBlog

© 2026 Ave syah Shina. All rights reserved.

  1. Home
  2. Blog
  3. 04 — R2 Storage: Object Storage Without Egress Fees

04 — R2 Storage: Object Storage Without Egress Fees

August 13, 20267 min read
Download as Markdown

"S3 but cheaper" was my R2 summary, and it undersold what the pricing actually does. The idea the whole product is built around: R2 is S3-compatible object storage whose structural innovation is zero egress fees — and that single change flips entire categories of application from 'too expensive to run' to 'trivial.' [1] The pricing model _is_ the feature.

The framing that landed is the unit economics of traditional object storage. With S3 and its peers, storage is cheap and writes are cheap, but every byte read back out — every image served, every video streamed, every backup downloaded — is billed at a per-GB egress rate. For a media-heavy app, egress dominates the bill, often by an order of magnitude. R2 removes that line item [1][2]. Store a terabyte, serve it a million times, pay for the terabyte of storage and effectively nothing for the serving.

S3-style pricing R2 pricing storage writes EGRESS (per GB) egress dominates the bill storage writes FREE read as much as you want the same data, served through Cloudflare's network S3-compatible API — tools and SDKs that target S3 work against R2 unchanged for a media app, the bill stops scaling with popularity

Object storage fundamentals

Object storage is the layer for unstructured data — files, blobs, media — as opposed to the structured rows of a database or the key-value pairs of KV [2]. The unit is an _object_ (a file plus its metadata), stored in a _bucket_, addressed by a key. There's no query language, no joins, no schema — just put, get, list, delete.

R2 implements the S3 API, which is the part that makes it drop-in. Anything written for S3 — the AWS SDK, the aws-sdk npm package, tools like rclone and the AWS CLI — works against R2 with a different endpoint URL and different credentials [1]. The way of thinking transfers wholesale; the code transfers almost wholesale.

Prerequisites that matter

Two prerequisites from the reference are worth flagging because they shape how I work with R2 in practice:

  • Node.js and npm. R2 is most often accessed from Workers, but the tooling around it — upload scripts, migration utilities, the AWS SDK when used outside Workers — is Node-shaped. Understanding npm and being comfortable in a Node REPL makes the surrounding workflow tractable [3].
  • TypeScript. Workers pair naturally with TypeScript, and R2 bindings are typed. Defining the shape of an upload's metadata, the structure of a manifest object, the options on a get — TypeScript turns what would be runtime errors into editor squiggles [4].

Serving assets through R2

The canonical pattern is R2-as-origin-behind-a-Worker. Static assets — images, videos, build artifacts, user uploads — live in a bucket, and a Worker sits in front, reading from the R2 binding and returning a Response [5]:

const object = await env.MY_BUCKET.get("images/avatar.png");
if (!object) return new Response("Not found", { status: 404 });
const headers = new Headers();
object.writeHttpMetadata(headers);
return new Response(object.body, { headers });

Because the Worker runs at the edge and R2 is integrated with Cloudflare's network, the response is served from near the user with no egress charge. For a static site or a media library, this replaces both a hosting provider and its bandwidth bill.

Large file handling

R2 and the Workers platform handle large files with two complementary techniques [6]:

  • Streaming. Rather than buffering a whole file into memory, you process it as a ReadableStream. A Worker can pipe a large upload directly to R2 or stream a large response back out, never holding the full bytes.
  • Range requests. For media playback, browsers ask for byte ranges (Range: bytes=0-1048575). R2 supports range gets natively, so a Worker can serve just the requested chunk — the basis for video seeking and adaptive streaming.

For genuinely huge video files, Cloudflare Stream (covered separately) is the higher-level product: it handles encoding, adaptive bitrate, and playback for you, with R2 underneath.

Bucket operations and lifecycle

Buckets are the top-level container. The lifecycle is straightforward [7][8]:

  • Create a bucket to house a category of data (one per environment, per service, per tenant).
  • Use it by uploading objects via the dashboard, wrangler, the S3 API, or a Worker.
  • Delete a bucket only after emptying it — buckets must be empty before deletion.

One nuance the reference flags: R2 doesn't ship the rich lifecycle-rules system that S3 has (automatic tiered expiration, transition to cold storage). For object expiration today, the pattern is custom logic — a scheduled Worker that scans and deletes based on a stored timestamp, or relying on a higher-level product like Images or Stream that owns its own lifecycle [8].

Asset management patterns

The two patterns I actually use:

  • Static asset storage for Pages. A Pages project can use an R2 bucket as its asset layer — uploads and large build artifacts live in R2, served through the Pages/Worker stack [5]. This is the natural home for anything too big to commit to Git.
  • On-the-fly transformation at the edge. A Worker reads an image from R2, transforms it (resize, format conversion via cf.image), and serves the result — caching the transformed version. R2 holds the source of truth; the edge holds the derivatives. This is where zero egress pays off: you can serve a thousand different resized variants of one source image and pay for none of the reads.

How I use this

R2 has become my default for any blob storage in a Workers project — the absence of egress removes the entire category of "can we afford to serve this?" anxiety. The pattern I keep returning to: one bucket per environment, a Worker in front that handles auth and content-type and caching, and a clear naming scheme for keys (media/{type}/{id}.{ext}). When I find myself wanting lifecycle rules or rich querying over the stored objects, that's the signal to move to a higher-level product (Images, Stream) rather than fight the raw bucket.

References

[1] Cloudflare, "Cloudflare R2 | Zero egress fee object storage," Cloudflare Developer Platform, 2024. [Online]. Available: https://www.cloudflare.com/developer-platform/products/r2/

[2] Cloudflare, "What is object storage?," Cloudflare Learning, 2024. [Online]. Available: https://www.cloudflare.com/learning/cloud/what-is-object-storage/

[3] Node.js Foundation, "Node.js documentation," nodejs.org. [Online]. Available: https://nodejs.org/en/

[4] TypeScript, "TypeScript documentation," typescriptlang.org. [Online]. Available: https://www.typescriptlang.org/docs/

[5] Cloudflare, "Use R2 as static asset storage with Cloudflare Pages," Cloudflare Pages Tutorials. [Online]. Available: https://developers.cloudflare.com/pages/tutorials/use-r2-as-static-asset-storage-for-pages/

[6] Cloudflare, "Cloudflare R2 limits," Cloudflare Docs. [Online]. Available: https://developers.cloudflare.com/r2/platform/limits/

[7] Cloudflare, "Buckets · Cloudflare R2," Cloudflare Docs. [Online]. Available: https://developers.cloudflare.com/r2/buckets/

[8] Cloudflare, "Bucket lifecycle · Cloudflare R2," Cloudflare API Reference. [Online]. Available: https://developers.cloudflare.com/api/resources/r2/subresources/buckets/subresources/lifecycle/

Knowledge check · Question 1 of 5

What is the structural pricing difference between R2 and traditional S3-style storage?

Comments

Leave a Comment

You must be signed in to comment

0 Comments

No comments yet. Be the first to comment!