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

## 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:

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

Example database schema:

```prisma
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:

```text
shopsphere-assets
```

Location:

```text
Automatic
```

Then click:

```text
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:

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

Cloudflare automatically displays this as:

```text
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.

* * *

## Recommended Object Structure

Avoid using names or slugs because they can change.

Instead:

```text
products/
├── product-id/
│   ├── image-1.webp
│   ├── image-2.webp
│   └── image-3.webp
```

Example:

```text
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:

```text
Account API Token
```

For learning or quick experiments:

```text
User API Token
```

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

For my application, I chose:

```text
Create Account API Token
```

* * *

## Step 6: Configure the Token

Token Name:

```text
ShopSphere Production
```

Permissions:

```text
Object Read & Write
```

Bucket Access:

```text
Specific Bucket
```

Choose:

```text
shopsphere-assets
```

TTL:

```text
Forever
```

Then click:

```text
Create Account API Token
```

Cloudflare will generate:

```text
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:

```env
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:

```bash
npm install @aws-sdk/client-s3
```

No Cloudflare-specific SDK is required.

* * *

## Step 9: Configure the R2 Client

Create:

```text
lib/r2.ts
```

```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:

```text
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:

```text
Browser
   ↓
Next.js API Route
   ↓
Cloudflare R2
```

Example:

```text
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:

```text
cdn.myapp.com
```

Your URLs become:

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

The nice part?

You don't need to move any files.

Only the URL changes.

* * *

## Recommended Setup for Small Projects

### Phase 1

```text
Database
Next.js Application
Private R2 Bucket
API Route
```

### Phase 2

```text
Purchase Domain
```

### Phase 3

```text
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.
