Skip to main content

Command Palette

Search for a command to run...

How to Connect Cloudflare R2 Storage to a Next.js App (Complete Beginner Guide)

Updated
β€’6 min read
How to Connect Cloudflare R2 Storage to a Next.js App (Complete Beginner Guide)
A
πŸ€– Curious AI enthusiast and Software Engineer with 7+ years of experience. πŸ› οΈ I enjoy getting my hands dirty with new technologies β€” experimenting, building side projects, and learning by doing. πŸš€ Currently exploring how AI can enhance real-world web applications.

Why I Chose Cloudflare R2

Recently, I was building a simple e-commerce application called ShopSphere. The application needed to store:

  • Product images

  • Category banners

  • User-uploaded documents

Initially, I considered several approaches:

  • Storing files inside the project repository

  • Supabase Storage

  • AWS S3

  • Cloudflare R2

I quickly ruled out storing images in the Git repository. User-generated files are data, not source code. Keeping uploads inside your repository can quickly bloat deployments and make version control difficult.

I wanted a solution that offered:

βœ… Generous free tier

βœ… No surprise bandwidth costs

βœ… Easy integration with Next.js

βœ… Ability to scale later

Cloudflare R2 ended up being an excellent fit.


What is Cloudflare R2?

Cloudflare R2 is an object storage service similar to Amazon S3.

Instead of storing files on your server, files are stored inside buckets and accessed via URLs.

Examples:

  • Product images

  • User profile photos

  • PDF documents

  • Videos

  • Application assets


Architecture

My setup looks like this:

Next.js Application
        ↓
API Route / Server Action
        ↓
Cloudflare R2
        ↓
Store File URL in Database

Example database schema:

model Product {
  id        String @id @default(cuid())
  name      String
  imageUrl  String
}

Instead of storing the actual file inside the database, we only store the URL.


Step 1: Create a Cloudflare Account

Visit:

https://dash.cloudflare.com

Create a free account and log in.


Step 2: Open R2 Object Storage

From the dashboard:

Storage & Databases ↓ R2 Object Storage

Click:

Create Bucket


Step 3: Create Your First Bucket

I named mine:

shopsphere-assets

Location:

Automatic

Then click:

Create Bucket

Congratulations! Your storage bucket is ready.


Step 4: Understanding "Folders" in R2

This was one of the first things that confused me.

You do NOT create folders manually.

Cloudflare R2 doesn't actually have folders.

It stores objects using keys.

For example:

products/abc123/product-1.webp
products/abc123/product-2.webp
products/xyz456/product-1.webp

Cloudflare automatically displays this as:

products/
β”œβ”€β”€ abc123/
β”‚   β”œβ”€β”€ product-1.webp
β”‚   └── product-2.webp
└── xyz456/
    └── product-1.webp

These folders don't physically exist.

They're simply prefixes inside object names.


Avoid using names or slugs because they can change.

Instead:

products/
β”œβ”€β”€ product-id/
β”‚   β”œβ”€β”€ image-1.webp
β”‚   β”œβ”€β”€ image-2.webp
β”‚   └── image-3.webp

Example:

products/cmgx123/image-1.webp

This approach scales much better.


Step 5: Creating API Credentials

Navigate to:

R2 Object Storage ↓ Overview ↓ Manage API Tokens

At this point, you'll see two options:

Option 1: Account API Tokens

These tokens belong to the Cloudflare account itself.

Examples:

  • Production applications

  • Server-side applications

  • Shared infrastructure

  • Team environments

Benefits:

βœ… Recommended by Cloudflare for production

βœ… Continues working even if team members change

βœ… Better suited for long-term projects


Option 2: User API Tokens

These tokens belong to your personal user account.

Examples:

  • Local development

  • Personal experiments

  • Temporary scripts

Benefits:

βœ… Quick to create

βœ… Good for testing

Drawbacks:

❌ Tied to your user account

❌ Less ideal for production systems


Which One Should You Choose?

For a real application, I recommend:

Account API Token

For learning or quick experiments:

User API Token

I initially found this screen confusing because both options looked very similar.

For my application, I chose:

Create Account API Token

Step 6: Configure the Token

Token Name:

ShopSphere Production

Permissions:

Object Read & Write

Bucket Access:

Specific Bucket

Choose:

shopsphere-assets

TTL:

Forever

Then click:

Create Account API Token

Cloudflare will generate:

Account ID
Access Key ID
Secret Access Key
Endpoint

⚠️ Important

The Secret Access Key is shown only once.

Copy it immediately and store it somewhere safe.


Step 7: Add Environment Variables

Inside Netlify:

R2_ACCOUNT_ID=
R2_ACCESS_KEY_ID=
R2_SECRET_ACCESS_KEY=
R2_BUCKET_NAME=shopsphere-assets
R2_ENDPOINT=https://<ACCOUNT_ID>.r2.cloudflarestorage.com

Step 8: Install the SDK

Cloudflare R2 uses an S3-compatible API.

Install the AWS SDK:

npm install @aws-sdk/client-s3

No Cloudflare-specific SDK is required.


Step 9: Configure the R2 Client

Create:

lib/r2.ts
import { S3Client } from "@aws-sdk/client-s3";

export const r2 = new S3Client({
  region: "auto",
  endpoint: process.env.R2_ENDPOINT,
  credentials: {
    accessKeyId: process.env.R2_ACCESS_KEY_ID!,
    secretAccessKey:
      process.env.R2_SECRET_ACCESS_KEY!,
  },
});

Step 10: Upload Flow

A typical upload process looks like this:

User Upload
      ↓
Validate file
      ↓
Resize image
      ↓
Convert to WebP
      ↓
Upload to R2
      ↓
Store URL in Database

What About Public URLs?

This part also confused me initially.

Depending on when you're reading this, Cloudflare's dashboard may look slightly different and you might not immediately see public access settings.

The good news is:

You don't need public URLs immediately.

For an MVP, this setup works perfectly:

Browser
   ↓
Next.js API Route
   ↓
Cloudflare R2

Example:

https://myapp.netlify.app/api/images/products/cmgx123/image-1.webp

Your API route:

  1. Reads the file from R2

  2. Streams it back

  3. Browser displays the image

Benefits:

βœ… No custom domain required

βœ… Bucket can remain private

βœ… Easy to add authentication

βœ… Better control over access


Later: Add a Custom Domain

Eventually, you may want something cleaner:

cdn.myapp.com

Your URLs become:

https://cdn.myapp.com/products/cmgx123/image-1.webp

The nice part?

You don't need to move any files.

Only the URL changes.


Phase 1

Database
Next.js Application
Private R2 Bucket
API Route

Phase 2

Purchase Domain

Phase 3

Custom CDN Domain
Public R2
Cloudflare CDN

Final Thoughts

Cloudflare R2 turned out to be surprisingly easy to work with.

It gives you:

  • Generous free storage

  • No egress fees

  • S3-compatible APIs

  • Excellent scalability

  • Simple integration with Next.js

If you're building an application that stores images, documents, or user uploads, Cloudflare R2 is definitely worth considering.