I am a...
Learn more
How it worksPricingFAQ
Account
May 24, 2026 · 11 min read · By Harsh Shuddhalwar

How to use Vercel Blob for file storage

vercel blob guide — How to use Vercel Blob for file storage
Photo by [Zulfugar Karimov](https://www.pexels.com/@zulfugarkarimov) on [Pexels](https://www.pexels.com/photo/organized-blue-office-binders-on-shelf-34293528/)

How to use Vercel Blob for file storage

Vercel Blob is a managed object-storage service built on Cloudflare R2 that lets a Next.js or any Node.js app upload, store, and serve files behind a global CDN with zero infrastructure setup. You install @vercel/blob, generate a read-write token in the Vercel dashboard, and either put() files from a server action or hand the browser a signed URL for direct client-side uploads. Storage costs $0.15/GB-month and egress to non-Vercel destinations costs $0.30/GB.

That's the short version. The longer version is where the trade-offs live: when to pick Blob over raw S3, how to wire up client-side uploads without leaking your token, and how pricing compounds once you have real traffic.

Why file storage is a 2026 problem again

For a decade, "use S3" was the answer to every file-storage question. AWS sets the floor on cost, gives you fine-grained IAM, and pairs with CloudFront for delivery. That's still true at scale.

What changed: most new SaaS apps ship on Vercel, Cloudflare Workers, or Fly, with teams of 2 to 5 engineers. The cost of configuring S3 (bucket, CORS, IAM, signed URLs, CloudFront, signed cookies for private content) is now larger than the cost of the storage for the first 18 months. Vercel Blob is the response: a thin product wrapper on Cloudflare R2 that trades a price premium for zero config.

What Vercel Blob actually is, under the hood

Vercel Blob is a managed bucket per project (or per Vercel team) backed by Cloudflare R2 storage, with a @vercel/blob SDK and an HTTP API on top. Every uploaded file gets a unique public URL on *.public.blob.vercel-storage.com and is served through Cloudflare's edge network.

Key facts:

  • Underlying store: Cloudflare R2 (S3-compatible object storage).
  • SDK: @vercel/blob, works in Node and Edge runtimes.
  • Max file size: 5 TB per object, but uploads >4.5 MB must use the client-upload path because Vercel functions cap request bodies at 4.5 MB.
  • Public vs private: Files are public-by-default via unguessable URLs. Private content needs server-generated signed URLs.
  • Token model: A single BLOB_READ_WRITE_TOKEN env var grants full bucket access. Don't expose it client-side.

The mental model: an S3 bucket your serverless functions can write to with one line, served over a CDN with no auth dance.

Pricing: the part nobody reads carefully

The Vercel Blob pricing page lists three line items. They behave very differently as your app grows.

Cost componentRate (2026)When it bites
Storage$0.15 / GB-monthLinear with total stored data. Predictable.
Egress to non-Vercel destinations$0.30 / GBSpikes if you serve videos or large downloads outside the Vercel CDN.
Egress to Vercel-hosted appsFreeIf your Next.js app pulls a Blob through <Image>, no egress charge.
Advanced operations$5 / 1M opsList, copy, head, multipart upload init. Hits hard if you do recursive sync.
Simple operationsIncluded in storageput, get, del on individual objects.

A worked example. Store 500 GB of user uploads, serve 2 TB/month to browsers (egress to non-Vercel because the browser is not a Vercel server):

  • Storage: 500 GB × $0.15 = $75/month
  • Egress: 2,000 GB × $0.30 = $600/month
  • Total: $675/month

The same workload on S3 + CloudFront: ~$11.50 storage + $170 egress = **$182/month**. That's a 3.7x premium for Blob. For 50 GB stored and 200 GB egress, the gap is closer to $67 vs $19. The crossover point is around 200 GB of monthly egress; past that, you're paying real money for convenience.

The default approach (and why it's flawed)

Most teams' first instinct on Vercel is to reach for S3 anyway, because that's what they know. They npm install @aws-sdk/client-s3, write a presigned-URL endpoint, handle CORS, set up CloudFront, configure cache headers, then spend a Friday afternoon debugging why their signed URLs return 403 in Safari.

This works. It's also four to six hours of yak-shaving that produces zero user-facing value on day one. For the first year of a product's life, every hour spent on storage plumbing is an hour not spent on the feature that determines whether the company exists in year two.

The flip side: teams that pick Blob and never check the bill are in for a surprise when a viral product launch pushes 5 TB of egress in a week.

The better approach: ship on Blob, monitor egress, migrate when the math flips

Here's the playbook we use on Cadence client projects.

1. Install and set up the token

npm install @vercel/blob

In the Vercel dashboard, go to your project, then Storage, then Create a Blob store. Vercel writes BLOB_READ_WRITE_TOKEN into your environment for all three deploy targets (production, preview, development). Run vercel env pull so local dev picks it up.

2. Server-side puts for small files

For anything under 4.5 MB (avatars, thumbnails, generated PDFs), upload directly from a Next.js Server Action:

'use server';

import { put } from '@vercel/blob';

export async function uploadAvatar(formData: FormData) {
  const file = formData.get('file') as File;
  if (!file) throw new Error('No file provided');

  const blob = await put(`avatars/${crypto.randomUUID()}-${file.name}`, file, {
    access: 'public',
    addRandomSuffix: false,
    contentType: file.type,
  });

  return { url: blob.url, pathname: blob.pathname };
}

The returned blob.url is a public CDN URL you can drop into a database, render with <Image>, or expose in an API response.

Why it matters: this is one function call. There's no presigned URL flow, no CORS preflight, no separate cache configuration.

What can go wrong: if file.size > 4.5 * 1024 * 1024, the Vercel function rejects the request body before your code runs. You'll see a generic 413. Switch to the client-upload path below for anything that might exceed it.

3. Client-side uploads via signed URLs for large files

For uploads larger than 4.5 MB (videos, design files, dataset CSVs), the browser uploads directly to Blob's edge, and your server only signs the request. The pattern uses @vercel/blob/client:

// app/api/upload/route.ts
import { handleUpload, type HandleUploadBody } from '@vercel/blob/client';
import { NextResponse } from 'next/server';
import { auth } from '@/lib/auth';

export async function POST(request: Request) {
  const body = (await request.json()) as HandleUploadBody;

  const jsonResponse = await handleUpload({
    body,
    request,
    onBeforeGenerateToken: async (pathname) => {
      const session = await auth();
      if (!session?.user) throw new Error('Unauthorized');

      return {
        allowedContentTypes: ['image/*', 'video/mp4', 'application/pdf'],
        maximumSizeInBytes: 500 * 1024 * 1024,
        tokenPayload: JSON.stringify({ userId: session.user.id }),
      };
    },
    onUploadCompleted: async ({ blob, tokenPayload }) => {
      const { userId } = JSON.parse(tokenPayload!);
      await db.uploads.insert({ userId, url: blob.url, pathname: blob.pathname });
    },
  });

  return NextResponse.json(jsonResponse);
}

On the client:

'use client';
import { upload } from '@vercel/blob/client';

async function handleFile(file: File) {
  const blob = await upload(file.name, file, {
    access: 'public',
    handleUploadUrl: '/api/upload',
  });
  return blob.url;
}

The browser bypasses your serverless function entirely on the data path. Your function only handles two small JSON round-trips: one to mint a signed token, one webhook when the upload finishes.

What can go wrong: skipping the auth check inside onBeforeGenerateToken is the single most common mistake. Without it, anyone on the internet can upload to your bucket. Always tie the token to a real session.

4. Use the right rendering surface

If you're rendering images, wrap the Blob URL in Next.js's <Image> component. Vercel sees the request, recognizes the Blob domain, and counts the egress as Vercel-to-Vercel (free). If you hotlink the raw Blob URL from a static HTML page hosted elsewhere, you're paying $0.30/GB.

5. Set up egress alerts before you ship a viral feature

In the Vercel dashboard, go to Settings, then Spend Management, and set a soft alert at 50% of your budget and a hard cap at 100%. Vercel will email you and (at the hard cap) start rejecting Blob requests rather than running up a $40k bill overnight. Founders who skip this step are the ones with the horror stories. The same discipline applies when you roll out feature flags on anything that touches large files.

6. Plan the migration path before you need it

If you hit 500 GB stored or 1 TB monthly egress, start modeling the cost on raw R2 (which Blob sits on top of) or S3 plus CloudFront. The migration tool we like is rclone, which can mirror a Blob bucket to S3 in a few hours for a typical mid-stage SaaS. Don't migrate prematurely; the rule of thumb is "the storage bill has to be larger than the engineer-week it costs to switch."

When Vercel Blob wins vs S3

ScenarioPick
You're shipping a Next.js app on Vercel and need file uploads next weekVercel Blob
You serve mostly images through <Image> on the same Vercel projectVercel Blob
You need fine-grained IAM (per-user buckets, cross-account access)S3
You have >1 TB stored or >2 TB monthly egressS3 + CloudFront
You need lifecycle policies (auto-delete after N days, glacier tier)S3
You're already on AWS for compute and want one billS3
You need S3 Object Lock for compliance (legal hold)S3
Team size: 2 to 5 engineers, no platform engineerVercel Blob
You want signed cookies for an entire path, not per-file URLsS3 + CloudFront
You're building a HIPAA-regulated app and need a BAAS3 (Vercel Blob doesn't sign BAAs as of 2026)

The HIPAA point is non-obvious and worth its own post; if that's your situation, start with our guide on HIPAA SaaS design from day one.

Common pitfalls

A few things bite teams in production:

  • Exposing BLOB_READ_WRITE_TOKEN to the browser. Anything prefixed with NEXT_PUBLIC_ is bundled into the client. The token is read-write on your entire bucket. A leaked token means anyone can upload anything, including content that gets you delisted from app stores.
  • Forgetting addRandomSuffix: false. By default, Blob appends a random suffix to your pathname. If you're using deterministic filenames for cache busting, this breaks your invalidation.
  • Treating Blob URLs as private. Public Blob URLs are unguessable but not authenticated. A user who shares one shares it forever. For genuinely private content, generate per-request signed URLs and set short expirations.
  • Not setting Cache-Control. Vercel sets a sane default, but if you're serving versioned assets you want Cache-Control: public, max-age=31536000, immutable. Pass it via the cacheControlMaxAge option to put().
  • Recursive list() calls in cron jobs. list is an advanced operation. Calling it across 100k objects every five minutes will surprise you on the bill. Cache the listing or paginate explicitly.

Replication, durability, and the boring questions

Cloudflare R2 replicates objects across multiple zones in a single region, with 11 nines of durability. There is no multi-region replication out of the box. If you need disaster recovery against a full Cloudflare region failure, roll your own mirror to a second provider, or accept that R2's track record means this is unlikely to matter early on.

For most SaaS startups, this is fine. For a fintech or healthtech app where file unavailability is a regulatory incident, plan a cross-provider mirror from day one.

When you can skip this entirely

If your app stores fewer than 100 files total (a marketing site, a B2B SaaS with occasional logo uploads), you don't need a file-storage strategy. Drop assets in your Git repo or use Vercel's static hosting. The Blob discussion only matters once user-generated content is real.

Same goes for apps where the file is the product (a video platform, a CAD tool). Those should be on raw R2 or S3 from day one because the storage bill will dwarf everything else.

The Cadence connection

Token setup, server actions, client-upload routes, and egress alerts are a four-to-six-hour job for a mid-tier engineer ($1,000/week on Cadence). Migration to S3 when you outgrow Blob is a senior-tier job ($1,500/week): rclone config, CloudFront, SDK calls, URL verification pass.

Every engineer on Cadence is AI-native, vetted on Cursor and Claude Code fluency before they unlock bookings, so they scaffold the boilerplate in an afternoon and spend the rest of the time on the parts that need a human (IAM policy review, upload UX edge cases). For deeper architecture context, see our best file upload services breakdown covering Uploadthing, Mux, and Cloudinary.

What to do next

Start with the cheapest test:

  1. Spin up a fresh Next.js project on Vercel.
  2. Create a Blob store from the dashboard.
  3. Build the avatar-upload flow from section 2.
  4. Ship it behind a feature flag to 5% of users.
  5. Watch the egress meter for two weeks.

If the numbers stay sane, you've avoided a week of S3 setup. If they climb fast, you have a real data point to justify the migration.

If you want a senior engineer to wire this up correctly the first time (token hygiene, signed-URL auth, alerting, sensible cache headers), audit your stack on Ship or Skip and book the work directly from the report. 48-hour free trial, weekly billing, replace the engineer any week.

FAQ

How big a file can I upload to Vercel Blob?

Up to 5 TB per object (the underlying R2 limit). Server-side put() calls are capped at 4.5 MB by Vercel's function body limit, so anything larger has to use the client-upload path via @vercel/blob/client.

Is Vercel Blob HIPAA-compliant?

No. As of 2026, Vercel does not sign Business Associate Agreements for the Blob product. If you're handling Protected Health Information, use S3 with a signed AWS BAA, or build on a HIPAA-eligible alternative like Aptible.

What's the difference between Vercel Blob and Cloudflare R2?

Blob is a managed product wrapper on R2 storage. R2 gives you raw S3-compatible API access at $0.015/GB-month with zero egress fees, but you handle CORS, signed URLs, and CDN config yourself. Blob is roughly 10x the storage price in exchange for a one-line SDK and Vercel-native integration.

Can I make Vercel Blob files private?

Yes. Set access: 'public' is the only documented option for the put() call, but you can keep the URL secret by not exposing it client-side, and you can generate short-lived signed URLs via the SDK for genuinely sensitive content. For strict per-request authorization, S3 with signed cookies remains the better fit.

How do I migrate from Vercel Blob to S3 later?

Use rclone with the R2-compatible endpoint to mirror your bucket to S3, then update the SDK calls in your app and switch the asset URLs. For a 200 GB bucket, the data copy takes a few hours and the code change is roughly a half-day for a senior engineer. The hardest part is updating any URLs already stored in your database.

Harsh Shuddhalwar
Fullstack Developer

Fullstack developer at withRemote. Ships across the stack — TypeScript, Node, Postgres, Vercel. Writes on shipping speed and pragmatic architecture.

All posts