How to Integrate Cloudflare R2 with Next.js 15 and Deploy on Vercel from GitHub (2025 Guide)

September 12, 2025
3 min read
How to Integrate Cloudflare R2 with Next.js 15 and Deploy on Vercel from GitHub (2025 Guide)

Quick TL;DR

  • Create R2 bucket + access keys in Cloudflare.
  • Add keys to Vercel environment variables.
  • Make a server API route in Next.js that creates a presigned PUT URL (using AWS S3 SDK).
  • Upload from the browser with the presigned URL.
  • Push to GitHub → connect to Vercel → deploy.
Short, no fluff — you’ll be done in minutes.

Why R2 is better than S3 (beginner-friendly)

  • Cheaper/no egress to Cloudflare CDN — R2 avoids heavy egress costs when paired with Cloudflare’s edge.
  • S3-compatible API — you can use the same AWS SDK code with a custom endpoint.
  • Simple pricing for static object storage — ideal for uploads & static assets.
  • Global edge delivery with Cloudflare built-in.

Downside: S3 still has deeper enterprise features. But for cost + performance, R2 wins for most web apps.

Step-by-step guide

1) Create R2 bucket + Access Keys

  1. Go to Cloudflare Dashboard → Workers & R2Create bucket.
  2. Find your Account ID (needed for endpoint).
  3. Create Access Key (Access Key ID + Secret). Save both.

2) Add environment variables in Vercel

R2_ACCOUNT_ID=your_account_id
R2_BUCKET=your_bucket_name
R2_ACCESS_KEY_ID=your_access_key
R2_SECRET_ACCESS_KEY=your_secret
R2_REGION=auto
  

3) Install AWS SDK in your Next.js app

npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
  

4) Create a server API route for presigned URL

// app/api/upload-url/route.ts
import { NextResponse } from "next/server";
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

const s3 = new S3Client({ region: process.env.R2_REGION || “auto”, endpoint: https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com, credentials: { accessKeyId: process.env.R2_ACCESS_KEY_ID || “”, secretAccessKey: process.env.R2_SECRET_ACCESS_KEY || “”, }, });

export async function POST(req: Request) { try { const { name, type } = await req.json(); const key = uploads/${Date.now()}-${Math.random().toString(36).slice(2,8)}-${name};

const cmd = new PutObjectCommand({
  Bucket: process.env.R2_BUCKET,
  Key: key,
  ContentType: type,
});

const url = await getSignedUrl(s3, cmd, { expiresIn: 60 });

return NextResponse.json({ url, key });

} catch (e: any) { return NextResponse.json({ error: e.message }, { status: 500 }); } }

5) Upload from client

async function uploadFile(file) {
  const res = await fetch("/api/upload-url", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ name: file.name, type: file.type })
  });
  const { url, key } = await res.json();

await fetch(url, { method: “PUT”, headers: { “Content-Type”: file.type }, body: file });

return { key, url }; }

6) Deploy on Vercel

  1. Push code to GitHub.
  2. Import repo into Vercel.
  3. Add same env vars in Vercel settings.
  4. Deploy — done!

Final Checklist

  • [ ] R2 bucket + Access Key created
  • [ ] Env vars in Vercel
  • [ ] Presigned URL route added
  • [ ] Client upload function works
  • [ ] GitHub → Vercel connected

End note: I personally run my site on R2 — it saves cost, gives me Cloudflare’s CDN edge, and still lets me use familiar S3 code. For most devs, it’s the best combo of speed + budget.

Recent Articles

The AI Hype Is Hitting a Wall Now

November 12, 2025

I’ve been hearing the narrative for two years. Everyone thinks AI is coming for all the jobs, starting with programming. But I am a developer, and from where I stand, the hype is settling down. …

Don't Call AI "The Future" Until You Know This

November 12, 2025

The market is currently overflowing with hype about Artificial Intelligence. You hear it everywhere: AI is the new revolution, the new internet, the new electricity. Alongside this hype, there’s …

Why I Chose Cloudflare Pages Over a VPS: The Best "True Free Host" for Static Sites

November 10, 2025

As a web developer who has built everything from small portfolios to complex agency sites, I’ve spent years navigating the hosting landscape. The debate between traditional hosting and modern …

Next.js Deployments: Vercel vs Cloudflare Stack (OpenNext, Workers, R2, D1)

November 9, 2025

Next.js Deployment: Vercel’s Walled Garden vs. The Cloudflare Superstack As a developer, I love Next.js. It’s a powerhouse. And naturally, the “easy button” for deployment is …

Deploy Your Hugo + Tailwind v4 Website to Cloudflare Pages Automatically with GitHub Actions

November 1, 2025

This guide shows how to automatically deploy a Hugo + Tailwind CSS site to Cloudflare Pages every time you push code to your GitHub repository. It’s written for beginners — no advanced DevOps …

How I Optimized Core Web Vitals to 100% on My HugoGo Website

October 31, 2025

Optimizing a HugoGo website to achieve a perfect 100% Core Web Vitals score is completely possible without major code rewrites. Hugo’s static site generation already gives a strong base — with server …