Why Teams Make This Move
Most migrations start with one of three pressures: cost predictability, tighter control over edge behavior, or a desire to consolidate services. Cloudflare can be a strong fit when your traffic profile is global and you want to manage caching and storage policy more directly.
The trade-off is that you move from a highly integrated product surface to a more explicit platform model. That is not a downside if your team is comfortable owning infrastructure decisions.
Migration Map Before You Touch Production
- Inventory every Vercel capability your app currently depends on
- Identify all data paths currently writing to Vercel Blob
- Define a cutover plan with clear rollback points
- Replicate analytics and monitoring first, not last
Vercel to Cloudflare Capability Mapping
Vercel Functions -> Cloudflare Workers
Vercel Blob -> Cloudflare R2
Vercel Edge -> Cloudflare edge network + cache rules
Vercel deploy -> Wrangler deployment pipelineStep 1: Move Blob Storage to R2
Treat storage migration as a separate project. The biggest mistakes happen when compute and storage are migrated in one rushed deployment.
Create and Bind an R2 Bucket
# Create bucket
wrangler r2 bucket create site-assets-prod
# Add binding in wrangler.toml
[[r2_buckets]]
binding = "ASSETS"
bucket_name = "site-assets-prod"Migrate Existing Objects
Export objects from Blob and import to R2 with deterministic keys. Keep object URLs stable where possible to avoid SEO and cache churn.
# Example pseudo workflow
node scripts/export-blob-manifest.js > blob-manifest.json
node scripts/sync-blob-to-r2.js --manifest blob-manifest.jsonReplace Storage Client in Next.js
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
const client = new S3Client({
region: "auto",
endpoint: process.env.R2_ENDPOINT,
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
},
});
await client.send(new PutObjectCommand({
Bucket: process.env.R2_BUCKET,
Key: key,
Body: fileBuffer,
ContentType: mimeType,
}));Step 2: Deploy Next.js with Wrangler
The goal is deterministic builds and predictable deploys. Keep your app behavior stable first, optimize runtime details second.
Install and Configure
npm install --save-dev wrangler
# Authenticate
wrangler loginname = "jackridgway-site"
compatibility_date = "2026-03-31"
[vars]
NODE_ENV = "production"
[[r2_buckets]]
binding = "ASSETS"
bucket_name = "site-assets-prod"Deploy Command in CI
npm ci
npm run build
npx wrangler deployStep 3: Handle Caching Intentionally
Vercel abstracted many caching defaults for you. On Cloudflare, be explicit about HTML vs static asset policy.
- Static assets: long TTL and immutable cache keys
- HTML: short TTL with safe revalidation strategy
- Uploads: versioned object keys to avoid stale cache collisions
Rollout Strategy That Minimizes Risk
- Run both platforms in parallel in pre-production
- Mirror a subset of production traffic to validate behavior
- Cut over DNS with a low TTL window
- Keep rollback runbook ready for 24 to 48 hours
Common Pitfalls
- Forgetting environment variable parity across pipelines
- Breaking signed URL behavior during Blob to R2 migration
- Assuming cache behavior is equivalent out of the box
- Shipping migration and feature work in the same release
Final Thought
A successful Vercel to Cloudflare migration is not about swapping one deployment command for another. It is about making runtime behavior, storage boundaries, and operational ownership explicit. Teams that treat this as a platform design exercise, not only a hosting change, usually finish with better reliability and clearer cost control.