Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/subratomandal/dyeink/llms.txt

Use this file to discover all available pages before exploring further.

Dyeink uses Cloudflare R2 for storing images, media files, and other user-uploaded content. R2 is S3-compatible and includes free egress, making it cost-effective for serving blog assets.

Prerequisites

  • A Cloudflare account (sign up at cloudflare.com)
  • Access to Cloudflare dashboard with billing configured
  • R2 subscription enabled (includes generous free tier)

Enable R2 in Cloudflare

1

Subscribe to R2

  1. Log in to your Cloudflare dashboard
  2. Navigate to R2 from the sidebar
  3. Click Purchase R2 Plan if not already enabled
  4. Review pricing and click Proceed
R2 includes 10 GB storage and 1 million Class A operations free per month. Perfect for getting started.
2

Note your Account ID

Your Account ID is displayed in the R2 overview page and in your URL:
https://dash.cloudflare.com/<ACCOUNT_ID>/r2
Copy and save this Account ID for your environment configuration.

Create R2 Bucket

1

Create a new bucket

  1. In the R2 dashboard, click Create bucket
  2. Choose a unique bucket name:
    • Development: dyeink-dev-uploads
    • Production: dyeink-uploads or dyeink-images
  3. Select a location hint (choose closest to your users)
  4. Click Create bucket
Bucket names must be globally unique across all Cloudflare R2 buckets and follow DNS naming conventions.
2

Configure bucket settings

  1. Click on your newly created bucket
  2. Review the Settings tab:
    • Storage class: Standard (default)
    • Object lifecycle rules: Optional, configure if needed

Configure Public Access

1

Enable public access (optional)

For serving public images and assets:
  1. In your bucket, navigate to Settings > Public access
  2. Click Allow Access
  3. You’ll receive a public bucket URL:
https://pub-<random-id>.r2.dev
Public buckets allow anyone to read objects. Only enable for publicly accessible content like blog images.
2

Custom domain (recommended)

For a branded URL like cdn.yourblog.com:
  1. In bucket settings, go to Settings > Custom Domains
  2. Click Connect Domain
  3. Enter your custom domain (e.g., cdn.yourblog.com)
  4. Follow the DNS configuration instructions
  5. Add the provided CNAME record to your domain’s DNS:
Type: CNAME
Name: cdn
Content: <bucket-id>.r2.cloudflarestorage.com
  1. Click Continue and wait for DNS propagation
Custom domains provide better branding and allow you to change storage providers without breaking image URLs.

Generate API Credentials

1

Create R2 API token

  1. Navigate to R2 > Overview
  2. Click Manage R2 API Tokens on the right sidebar
  3. Click Create API token
  4. Configure the token:
    • Token name: Dyeink Production (or appropriate name)
    • Permissions:
      • Select Object Read & Write
    • TTL: Leave as “Forever” or set expiration
    • Bucket scope: Select specific buckets or all buckets
  5. Click Create API Token
2

Save credentials

You’ll see three important values:
Access Key ID: <20-character key>
Secret Access Key: <40-character secret>
Endpoint: https://<account-id>.r2.cloudflarestorage.com
The Secret Access Key is only shown once. Save it immediately in a secure location.
Copy these values for your environment configuration.

Environment Configuration

Backend (backend/.env)

# Cloudflare R2 Storage
R2_ACCOUNT_ID=your-cloudflare-account-id
R2_ACCESS_KEY_ID=your-r2-access-key-id
R2_SECRET_ACCESS_KEY=your-r2-secret-access-key
R2_BUCKET_NAME=dyeink-images
R2_PUBLIC_URL=https://your-r2-bucket.r2.dev

Root (.env)

# Cloudflare R2 Storage
R2_ACCOUNT_ID=your-cloudflare-account-id
R2_ACCESS_KEY_ID=your-r2-access-key-id
R2_SECRET_ACCESS_KEY=your-r2-secret-access-key
R2_BUCKET_NAME=dyeink-uploads
R2_PUBLIC_URL=https://pub-xxxx.r2.dev
Never commit R2 credentials to version control. Use environment variables or secrets management.

S3-Compatible Client Setup

R2 is S3-compatible, so you can use standard S3 client libraries.
import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';

const s3Client = new S3Client({
  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,
  },
});

// Upload a file
async function uploadFile(key, body, contentType) {
  const command = new PutObjectCommand({
    Bucket: process.env.R2_BUCKET_NAME,
    Key: key,
    Body: body,
    ContentType: contentType,
  });

  await s3Client.send(command);
  return `${process.env.R2_PUBLIC_URL}/${key}`;
}

Configure CORS

If your frontend needs direct access to R2, configure CORS settings.
1

Set CORS policy

  1. Navigate to your bucket in the R2 dashboard
  2. Go to Settings > CORS policy
  3. Add a CORS rule:
[
  {
    "AllowedOrigins": [
      "http://localhost:5173",
      "https://yourdomain.com"
    ],
    "AllowedMethods": [
      "GET",
      "PUT",
      "POST",
      "DELETE"
    ],
    "AllowedHeaders": [
      "*"
    ],
    "ExposeHeaders": [
      "ETag"
    ],
    "MaxAgeSeconds": 3600
  }
]
  1. Click Save
2

Test CORS

Make a request from your frontend to verify CORS is working:
fetch('https://your-bucket.r2.dev/test.jpg', {
  method: 'GET',
})
  .then(response => console.log('CORS working!', response))
  .catch(error => console.error('CORS error:', error));

CDN and Caching

R2 integrates seamlessly with Cloudflare’s CDN for optimal performance.
1

Enable Cloudflare CDN

If using a custom domain:
  1. Ensure your domain is proxied through Cloudflare (orange cloud icon)
  2. Cloudflare will automatically cache static assets
  3. Configure cache rules in Caching > Configuration
2

Set cache headers

Configure appropriate cache headers when uploading:
const command = new PutObjectCommand({
  Bucket: process.env.R2_BUCKET_NAME,
  Key: key,
  Body: body,
  ContentType: contentType,
  CacheControl: 'public, max-age=31536000, immutable', // 1 year
});
Use versioned filenames (e.g., image-v2.jpg) with long cache times for better performance.
3

Purge cache when needed

Use Cloudflare’s API or dashboard to purge cached files:
  1. Navigate to Caching > Configuration
  2. Click Purge Everything or Custom Purge
  3. Enter specific URLs to purge

Verification

1

Test upload

Create a test script to upload a file:
test-r2.js
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';

const s3Client = new S3Client({
  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,
  },
});

async function testUpload() {
  try {
    const command = new PutObjectCommand({
      Bucket: process.env.R2_BUCKET_NAME,
      Key: 'test.txt',
      Body: 'Hello, R2!',
      ContentType: 'text/plain',
    });

    await s3Client.send(command);
    console.log('✓ Upload successful!');
    console.log(`URL: ${process.env.R2_PUBLIC_URL}/test.txt`);
  } catch (error) {
    console.error('✗ Upload failed:', error.message);
  }
}

testUpload();
Run it:
node test-r2.js
2

Verify in dashboard

  1. Navigate to your bucket in R2 dashboard
  2. You should see test.txt in the object list
  3. Click on it to view details and download
3

Test public access

Visit the public URL in your browser:
https://your-bucket.r2.dev/test.txt
You should see “Hello, R2!” displayed.

Best Practices

  • Use unique, versioned filenames to avoid cache issues
  • Implement image optimization before uploading (resize, compress)
  • Store original files separately from optimized versions
  • Use appropriate content types for all uploads
  • Implement file size limits to prevent abuse
  • Generate thumbnails and multiple sizes for responsive images
  • Use signed URLs for private content

Example: Image Upload with Optimization

import sharp from 'sharp';
import { nanoid } from 'nanoid';

async function uploadImage(file) {
  // Generate unique filename
  const filename = `${nanoid()}.webp`;

  // Optimize image
  const optimized = await sharp(file.buffer)
    .resize(1200, 1200, { fit: 'inside', withoutEnlargement: true })
    .webp({ quality: 80 })
    .toBuffer();

  // Upload to R2
  const command = new PutObjectCommand({
    Bucket: process.env.R2_BUCKET_NAME,
    Key: `images/${filename}`,
    Body: optimized,
    ContentType: 'image/webp',
    CacheControl: 'public, max-age=31536000, immutable',
  });

  await s3Client.send(command);

  return `${process.env.R2_PUBLIC_URL}/images/${filename}`;
}

Troubleshooting

  • Verify R2_ACCESS_KEY_ID and R2_SECRET_ACCESS_KEY are correct
  • Check that the API token has appropriate permissions
  • Ensure the bucket name matches exactly
  • Verify the token hasn’t expired
  • Verify CORS policy is configured in bucket settings
  • Check that AllowedOrigins matches your frontend domain exactly
  • Ensure AllowedMethods includes the HTTP method you’re using
  • Clear browser cache and try again
  • Verify public access is enabled for the bucket
  • Check that the object key (filename) is correct
  • Ensure the file was uploaded successfully (check R2 dashboard)
  • Wait a few minutes for DNS propagation if using custom domain
  • Check your network connection
  • Verify you’re using the correct region endpoint
  • Consider using Cloudflare’s CDN with a custom domain
  • Implement multipart uploads for large files

Cost Optimization

R2 pricing (as of 2026):
  • Storage: $0.015/GB/month (first 10 GB free)
  • Class A operations (writes): $4.50/million (first 1M free)
  • Class B operations (reads): $0.36/million (first 10M free)
  • Egress: FREE (no bandwidth charges)
  • Delete unused files and old versions regularly
  • Implement lifecycle policies to automatically delete temporary files
  • Use image optimization to reduce storage costs
  • Monitor usage in Cloudflare Analytics
  • Consider implementing a file retention policy

Security Best Practices

  • Never expose R2 credentials in frontend code
  • Use signed URLs for private content instead of public buckets
  • Rotate API tokens regularly
  • Implement file type validation before upload
  • Scan uploaded files for malware
  • Set up rate limiting for upload endpoints
  • Use separate buckets for development and production
  • Monitor access logs for suspicious activity

Next Steps

Deployment

Deploy your Dyeink platform to production

Auth0 Setup

Configure authentication and authorization

Build docs developers (and LLMs) love