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

How I Fixed My CI/CD: Fast, Optimized, and Pro-Level Deploys (Hugo + Cloudflare)

January 14, 2026

We’ve all been there: you push a tiny CSS tweak, and then you sit… and wait… and watch the GitHub Actions logs spin. My Hugo site was taking nearly a minute to deploy. Not …

Deploy Hugo + Tailwind v4 to Cloudflare: Super Fast GitHub Workflow

January 13, 2026

So you want your Hugo site to fly, right? Like, blink-and-it’s-live kind of speed. I got you. We’re gonna set up a GitHub Actions workflow that takes your Hugo code (rocking Tailwind v4), builds it …

Is Tailwind CSS Dying in 2026? Why v4 Might Be the Framework's Final Masterpiece

January 13, 2026

Hope you’re doing good! Honestly, there’s some pretty wild news in the Tailwind world right now. It’s a mix of “the tech is amazing” and “the business is in trouble.” …

Why Hugo is the best Static Site Generators in 2026

January 13, 2026

Thinking about building a site in 2026? Honestly, the “shiny object syndrome” is real with all these frameworks, but Hugo is still the low-key king for anyone who wants a site that just …

Windows 11 vs. The Linux Crew: Why It’s Time to Jump Ship

January 13, 2026

If you’re tired of Windows 11 acting like that one overbearing landlord who enters your apartment without knocking, it’s time we talk about Linux. Switching OSs sounds like a massive headache, but …

Upgrading openSUSE Leap 16.0 to Kernel 6.18 (LTS) via Backports

January 12, 2026

Look, openSUSE Leap 16.0 is awesome. It’s stable, it’s enterprise-grade, and it doesn’t crash. But sometimes, “stable” feels a little too much like “driving your …