Skip to content

file storage · side by side

Cloudflare R2 vs Vercel Blob for a Next.js app

Both fill the file storage slot, so a generated repo carries one or the other, never both. Every line below is read out of the two manifests.

Short answer

Pick Cloudflare R2 if

Anything read far more often than it is written, and anything large. User uploads, video, model weights, datasets, backups you may need to pull out again.

Pick Vercel Blob if

Apps already on Vercel that want user uploads (avatars, attachments, imports) without adding a cloud account, IAM, CORS rules or a second bill. Strongest when files are small to medium and read by their owner.

Side by side

Price, obligations, and the surface each one adds. No row is written by hand. This is manifest.yaml, rendered.

Cloudflare R2 compared with Vercel Blob on pricing, fit, trade-offs, required companions, environment variables, dependencies, MCP servers and repo footprint.
From the manifestOption ACloudflare R2Option BVercel Blob
In one lineCloudflare R2

S3-compatible object storage with free egress. You pay for storage and requests.

Vercel Blob

File storage inside your Vercel project. One env var to wire, no second vendor.

PricingCloudflare R2

Free each month: 10 GB stored, 1M Class A and 10M Class B operations. Then $0.015 per GB-month, $4.50 per million Class A operations (writes, lists) and $0.36 per million Class B operations (reads). Egress is free.

Vercel Blob

Hobby: free up to 1 GB stored, 10,000 simple and 2,000 advanced operations, and 10 GB of transfer a month. Past the limit, Blob stops until the 30 days reset. Pro bills by usage. Prices below are for iad1. Storage is $0.023 per GB-month and transfer $0.05 per GB. Simple operations (cache-miss reads) are $0.40 per million. Advanced operations (put, copy, list) are $5 per million. Deletes are free. Client uploads have no transfer charge.

Best forCloudflare R2

Anything read far more often than it is written, and anything large. User uploads, video, model weights, datasets, backups you may need to pull out again.

Vercel Blob

Apps already on Vercel that want user uploads (avatars, attachments, imports) without adding a cloud account, IAM, CORS rules or a second bill. Strongest when files are small to medium and read by their owner.

Trade-offsVerbatim from the manifestCloudflare R2
  • No egress fee, but operations are billed. Many tiny reads can cost more than the bytes. Check Class B pricing against your access pattern, not just your storage volume.
  • S3-compatible, not S3. The common surface works with the AWS SDK. Parts of the long tail (some checksum modes, ACLs, object lock, storage classes) do not. Test, do not assume.
  • Authorisation is your job. R2 has no row level security, so "may this user read this key" is a decision your app makes on every request.
  • A bucket is private until you attach a custom domain or enable the r2.dev subdomain, and r2.dev is rate-limited and not for production. Public serving means DNS work, not a checkbox.
  • Strong read-after-write consistency, but no built-in image transforms. That is a separate Cloudflare product, or a resize on upload in your own code.
  • Bucket config (CORS, lifecycle) lives in Cloudflare, not in your migrations. It needs its own committed files and a command to apply them. This battery ships both.
Vercel Blob
  • Egress is billed. Blob data transfer is cheaper than Vercel's regular CDN transfer, but it is not zero. Large media served at volume is the case R2 exists for.
  • A store is private or public forever. You pick at creation and cannot change it. Mixing both means two stores.
  • Advanced operations are the expensive line. Every put, copy and list counts, and so does browsing the store in the Vercel dashboard.
  • Authorisation is your code. Blob has no row level security. The upload route decides who may write and where, every time.
  • onUploadCompleted is a webhook from Vercel to your app. It cannot reach localhost, so local testing needs a tunnel.
  • Overwrites and deletes take up to 60 seconds to leave the cache. Treat blobs as immutable and give every version a new pathname.
Required companionsAdded for you, with a reasonCloudflare R2

Nothing. It stands on its own.

Vercel Blob

Nothing. It stands on its own.

Recommended alongsideSuggested, never added for youCloudflare R2
  • An auth battery
Vercel Blob
  • An auth battery
Env vars you will manageEvery one documented in docs/onboard.mdCloudflare R2

7 variables · 4 required

  • R2_ACCESS_KEY_ID
  • R2_ACCOUNT_ID
  • R2_BUCKET
  • R2_SECRET_ACCESS_KEY
  • R2_PUBLIC_BASE_URL
  • STORAGE_ALLOWED_ORIGINS
  • STORAGE_DEV_UPLOADER
Vercel Blob

4 variables · 1 required

  • BLOB_READ_WRITE_TOKEN
  • STORAGE_ALLOWED_ORIGINS
  • STORAGE_DEV_UPLOADER
  • VERCEL_BLOB_CALLBACK_URL
Dependencies addedCloudflare R2
  • @aws-sdk/client-s3 ^3.1141.0
  • @aws-sdk/s3-request-presigner ^3.1141.0
  • server-only ^0.0.1
Vercel Blob
  • @vercel/blob ^2.8.0
  • server-only ^0.0.1
MCP serversWritten into .mcp.jsonCloudflare R2

None. No extra agent tools from this one.

Vercel Blob

None. No extra agent tools from this one.

Footprint in your repoCloudflare R2

13 files, plus 3 injections into shared stack files

Vercel Blob

13 files, plus 3 injections into shared stack files

What changes in your repo

The paths each battery contributes, diffed. A path in the third list is written by both, so swapping rewrites that file rather than adding one.

Only with Cloudflare R2 (3)

  • infra/2 files
    • r2/2 files
      • cors.json
      • lifecycle.json
  • scripts/1 file
    • r2/1 file
      • apply-config.ts

Only with Vercel Blob (3)

  • src/2 files
    • lib/2 files
      • storage/2 files
        • access.ts
        • confirm.ts
  • variants/1 file
    • auth-clerk/1 file
      • slots/1 file
        • auth-public-routes.ts

Same path, different implementation (10)

  • src/5 files
    • app/1 file
      • api/1 file
        • upload/1 file
          • route.ts
    • components/1 file
      • upload/1 file
        • file-dropzone.tsx
    • lib/3 files
      • storage/3 files
        • authorize.ts
        • index.ts
        • keys.ts
  • tests/1 file
    • unit/1 file
      • storage-keys.test.ts
  • variants/4 files
    • auth-none/1 file
      • src/1 file
        • lib/1 file
          • storage/1 file
            • uploader.ts
    • auth-wired/1 file
      • src/1 file
        • lib/1 file
          • storage/1 file
            • uploader.ts
    • errors-none/1 file
      • src/1 file
        • lib/1 file
          • storage/1 file
            • report.ts
    • errors-sentry/1 file
      • src/1 file
        • lib/1 file
          • storage/1 file
            • report.ts

Shared stack files Cloudflare R2 injects into

  • env-required
  • legal-processors
  • verify-checks

Shared stack files Vercel Blob injects into

  • env-required
  • legal-processors
  • verify-checks

Cloudflare R2 in your .env.local

.env.localbash10 lines
# required
R2_ACCESS_KEY_ID=0123456789abcdef0123456789abcdef
R2_ACCOUNT_ID=0123456789abcdef0123456789abcdef
R2_BUCKET=uploads
R2_SECRET_ACCESS_KEY=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef

# optional
R2_PUBLIC_BASE_URL=https://files.example.com
STORAGE_ALLOWED_ORIGINS=https://app.example.com,https://staging.example.com
STORAGE_DEV_UPLOADER=dev-user

Vercel Blob in your .env.local

.env.localbash7 lines
# required
BLOB_READ_WRITE_TOKEN=vercel_blob_rw_0123456789abcdef_0123456789abcdef0123456789abcdef

# optional
STORAGE_ALLOWED_ORIGINS=https://app.example.com,https://staging.example.com
STORAGE_DEV_UPLOADER=dev-user
VERCEL_BLOB_CALLBACK_URL=

What changes for your agents

Each battery ships rules, skills, subagents and hooks that an agent loads before it touches the code that battery owns. Picking one is also picking how your agents behave in src/lib/storage/**.

Cloudflare R2

  • 2

    Skills

  • 2

    Rules

  • 5

    Solution docs

Rules (2)

Bucket configuration is code, and R2 is S3-compatible rather than S3

infra/r2/** · scripts/r2/** · src/lib/storage/**

Never sign a URL without an authorisation check, and never proxy uploads through a route

src/lib/storage/** · src/app/api/upload/** · src/components/upload/**

Skills (2)

/add-bucket

Add a second R2 bucket for a different access model, with its CORS and lifecycle configuration committed as code and applied by script rather than clicked into the dashboard.

/upload-flow

Add a complete file upload to a feature: authorised signing route, direct-to-R2 PUT, the database row that records the key, and the cleanup that stops orphans.

Subagents and hooks

None of its own. The foundation agents and guard hooks still ship.

Vercel Blob

  • 2

    Skills

  • 2

    Rules

  • 7

    Solution docs

Rules (2)

Vercel Blob store - access mode, credentials, cache, cost

src/lib/storage/** · scripts/verify.ts

Client uploads - auth before the token, the server names the key, the token is locked down

src/lib/storage/** · src/app/api/upload/** · src/components/upload/**

Skills (2)

/add-upload-kind

Add a new kind of upload (avatars, attachments, imports) to the Vercel Blob flow, with its own folder, type and size limits, the row that owns the key, and the cleanup that stops orphans.

/clean-orphaned-blobs

Find and delete Vercel Blob objects no database row points at, safely, in batches, without deleting uploads that are still in flight.

Subagents and hooks

None of its own. The foundation agents and guard hooks still ship.

What each one already knows

Solution docs land in docs/solutions/ in your repo and are published here, so you can read the failure modes before you commit.

Cloudflare R2 (5)

Vercel Blob (7)

Which one to pick

From meta.bestFor and meta.tradeoffs. If a claim is not in the manifest, it is not on this page.

Pick Cloudflare R2 when

Anything read far more often than it is written, and anything large. User uploads, video, model weights, datasets, backups you may need to pull out again.

And accept that(6)
  • No egress fee, but operations are billed. Many tiny reads can cost more than the bytes. Check Class B pricing against your access pattern, not just your storage volume.
  • S3-compatible, not S3. The common surface works with the AWS SDK. Parts of the long tail (some checksum modes, ACLs, object lock, storage classes) do not. Test, do not assume.
  • Authorisation is your job. R2 has no row level security, so "may this user read this key" is a decision your app makes on every request.
  • A bucket is private until you attach a custom domain or enable the r2.dev subdomain, and r2.dev is rate-limited and not for production. Public serving means DNS work, not a checkbox.
  • Strong read-after-write consistency, but no built-in image transforms. That is a separate Cloudflare product, or a resize on upload in your own code.
  • Bucket config (CORS, lifecycle) lives in Cloudflare, not in your migrations. It needs its own committed files and a command to apply them. This battery ships both.

Pick Vercel Blob when

Apps already on Vercel that want user uploads (avatars, attachments, imports) without adding a cloud account, IAM, CORS rules or a second bill. Strongest when files are small to medium and read by their owner.

And accept that(6)
  • Egress is billed. Blob data transfer is cheaper than Vercel's regular CDN transfer, but it is not zero. Large media served at volume is the case R2 exists for.
  • A store is private or public forever. You pick at creation and cannot change it. Mixing both means two stores.
  • Advanced operations are the expensive line. Every put, copy and list counts, and so does browsing the store in the Vercel dashboard.
  • Authorisation is your code. Blob has no row level security. The upload route decides who may write and where, every time.
  • onUploadCompleted is a webhook from Vercel to your app. It cannot reach localhost, so local testing needs a tunnel.
  • Overwrites and deletes take up to 60 seconds to leave the cache. Treat blobs as immutable and give every version a new pathname.

Questions people actually ask

Should I choose Cloudflare R2 or Vercel Blob?
Cloudflare R2 is best for anything read far more often than it is written, and anything large. User uploads, video, model weights, datasets, backups you may need to pull out again. Vercel Blob is best for apps already on Vercel that want user uploads (avatars, attachments, imports) without adding a cloud account, IAM, CORS rules or a second bill. Strongest when files are small to medium and read by their owner. Both fill the file storage slot, so a generated repo carries one or the other, never both.
How much do Cloudflare R2 and Vercel Blob cost?
Cloudflare R2: Free each month: 10 GB stored, 1M Class A and 10M Class B operations. Then $0.015 per GB-month, $4.50 per million Class A operations (writes, lists) and $0.36 per million Class B operations (reads). Egress is free. Vercel Blob: Hobby: free up to 1 GB stored, 10,000 simple and 2,000 advanced operations, and 10 GB of transfer a month. Past the limit, Blob stops until the 30 days reset. Pro bills by usage. Prices below are for iad1. Storage is $0.023 per GB-month and transfer $0.05 per GB. Simple operations (cache-miss reads) are $0.40 per million. Advanced operations (put, copy, list) are $5 per million. Deletes are free. Client uploads have no transfer charge.
What changes in my repo if I switch from Cloudflare R2 to Vercel Blob?
Cloudflare R2 writes 13 files, 7 environment variables and 3 dependencies, and installs 2 path-scoped rules, 2 skills and 5 solution docs. Vercel Blob writes 13 files, 4 environment variables and 2 dependencies, and installs 2 path-scoped rules, 2 skills and 7 solution docs.

Decide once, then build the repo that already knows the decision.

Either way you get that choice’s rules, skills and solution docs installed, plus the guard hooks, an onboarding doc for exactly these env vars, and the Compound Engineering loop. Free and MIT.