Cloudflare R2 for Media Management
Overview
Cloudflare R2 is an S3-compatible object storage service that offers zero egress fees, making it an excellent choice for hosting static media assets like images, videos, and other files for your VitePress website. This guide will show you how to set up and integrate Cloudflare R2 with your static site.
Why Use Cloudflare R2?
- Zero egress fees: Unlike other cloud storage providers, R2 doesn't charge for data transfer out
- S3 compatibility: Works with existing S3 tools and libraries
- Global CDN: Automatic caching through Cloudflare's global network
- Cost-effective: Competitive pricing for storage
- High performance: Fast content delivery worldwide
Setting Up Cloudflare R2
Step 1: Create a Cloudflare Account
- Visit Cloudflare and create an account
- Navigate to the R2 Object Storage section in your dashboard
- Enable R2 Object Storage (you may need to add a payment method)
Step 2: Create an R2 Bucket
- Click "Create bucket"
- Choose a unique bucket name (e.g.,
my-website-media
) - Select a location hint (closest to your users)
- Click "Create bucket"
Step 3: Configure Bucket Settings
Public Access Configuration
For a static website, you'll want to make your media files publicly accessible:
- Go to your bucket settings
- Navigate to "Settings" tab
- Under "Public access", click "Allow Access"
- Configure custom domain (optional but recommended)
Custom Domain Setup (Recommended)
- In your bucket settings, go to "Settings"
- Click "Connect Domain"
- Enter your custom domain (e.g.,
media.yourdomain.com
) - Add the required DNS records to your domain:
Type: CNAME Name: media Target: <your-bucket-name>.r2.cloudflarestorage.com
Organizing Your Media Files
Recommended Folder Structure
your-bucket/
├── images/
│ ├── blog/
│ │ ├── 2024/
│ │ └── thumbnails/
│ ├── gallery/
│ │ ├── nikon-d100/
│ │ └── nikon-zfc/
│ └── ui/
│ ├── icons/
│ └── logos/
├── videos/
│ ├── tutorials/
│ └── demos/
├── documents/
│ └── pdfs/
└── assets/
├── fonts/
└── styles/
File Naming Conventions
- Use lowercase letters and hyphens
- Include dates for blog content:
2024-01-15-tutorial-screenshot.jpg
- Use descriptive names:
jetson-yolo-demo-video.mp4
- Consider file sizes and optimize before uploading
Uploading Files to R2
Method 1: Web Interface
- Open your R2 bucket in the Cloudflare dashboard
- Click "Upload" button
- Drag and drop files or browse to select them
- Files will be uploaded and immediately available
Method 2: CLI Tools
Using Rclone (Recommended)
Install Rclone:
bash# On macOS brew install rclone # On Ubuntu/Debian sudo apt install rclone # On Windows # Download from https://rclone.org/downloads/
Configure Rclone for R2:
bashrclone config
Follow the prompts:
- Choose "Amazon S3 Compliant Storage Providers"
- Provider: "Cloudflare R2"
- Enter your R2 credentials (from Cloudflare dashboard > R2 > Manage R2 API tokens)
Upload files:
bash# Upload a single file rclone copy local-file.jpg r2:your-bucket-name/images/ # Upload entire directory rclone sync ./local-media-folder r2:your-bucket-name/ # Upload with progress rclone copy ./images r2:your-bucket-name/images/ --progress
Using AWS CLI
Install AWS CLI
Configure with R2 credentials:
bashaws configure # Use your R2 access keys # Set region to "auto" # Set endpoint: https://<account-id>.r2.cloudflarestorage.com
Upload files:
bashaws s3 cp image.jpg s3://your-bucket-name/images/ --endpoint-url https://<account-id>.r2.cloudflarestorage.com
Integrating R2 with VitePress
Method 1: Direct URLs in Markdown
<!-- Using custom domain -->

<!-- Using R2 direct URL -->

<!-- Video embed -->
<video controls>
<source src="https://media.yourdomain.com/videos/demo.mp4" type="video/mp4">
</video>
Method 2: Create a Media Helper
Create a composable for managing media URLs:
// .vitepress/composables/useMedia.ts
export const useMedia = () => {
const MEDIA_BASE_URL = 'https://media.yourdomain.com'
const getImageUrl = (path: string) => `${MEDIA_BASE_URL}/images/${path}`
const getVideoUrl = (path: string) => `${MEDIA_BASE_URL}/videos/${path}`
const getDocumentUrl = (path: string) => `${MEDIA_BASE_URL}/documents/${path}`
return {
getImageUrl,
getVideoUrl,
getDocumentUrl
}
}
Use in your Vue components:
<template>
<img :src="getImageUrl('blog/2024/tutorial-screenshot.jpg')" alt="Tutorial Screenshot">
</template>
<script setup>
import { useMedia } from '../.vitepress/composables/useMedia'
const { getImageUrl } = useMedia()
</script>
Method 3: Environment-Based Configuration
// .vitepress/config.mts
export default defineConfig({
define: {
__MEDIA_BASE_URL__: JSON.stringify(
process.env.NODE_ENV === 'production'
? 'https://media.yourdomain.com'
: '/local-media'
)
},
// ... rest of config
})
Optimization Best Practices
Image Optimization
Use WebP format for better compression:
bash# Convert images to WebP cwebp input.jpg -q 80 -o output.webp
Create multiple sizes for responsive images:
markdown<picture> <source srcset="https://media.yourdomain.com/images/photo-800w.webp" media="(max-width: 800px)"> <source srcset="https://media.yourdomain.com/images/photo-1200w.webp" media="(max-width: 1200px)"> <img src="https://media.yourdomain.com/images/photo-1600w.webp" alt="Description"> </picture>
Video Optimization
Use appropriate codecs:
- H.264 for broad compatibility
- H.265/HEVC for better compression
- VP9/AV1 for modern browsers
Create multiple qualities:
bash# Using FFmpeg ffmpeg -i input.mp4 -b:v 1M -maxrate 1M -bufsize 2M output-1080p.mp4 ffmpeg -i input.mp4 -b:v 500k -maxrate 500k -bufsize 1M output-720p.mp4
Caching Configuration
Configure appropriate cache headers in Cloudflare:
- Go to your domain in Cloudflare dashboard
- Navigate to "Caching" > "Cache Rules"
- Create a rule for your media subdomain:
- If: Hostname equals
media.yourdomain.com
- Then: Cache Level = Standard, Browser TTL = 1 month, Edge TTL = 1 month
- If: Hostname equals
Automation Scripts
Bulk Upload Script
#!/bin/bash
# upload-media.sh
BUCKET_NAME="your-bucket-name"
LOCAL_DIR="./media"
R2_PREFIX="r2:$BUCKET_NAME"
echo "Starting upload to R2..."
# Upload images
rclone sync "$LOCAL_DIR/images" "$R2_PREFIX/images/" \
--progress \
--exclude "*.DS_Store" \
--exclude "Thumbs.db"
# Upload videos
rclone sync "$LOCAL_DIR/videos" "$R2_PREFIX/videos/" \
--progress \
--exclude "*.DS_Store"
echo "Upload completed!"
Image Processing Pipeline
#!/bin/bash
# process-images.sh
INPUT_DIR="./raw-images"
OUTPUT_DIR="./processed-images"
BUCKET_NAME="your-bucket-name"
mkdir -p "$OUTPUT_DIR"
for img in "$INPUT_DIR"/*.{jpg,jpeg,png}; do
if [ -f "$img" ]; then
filename=$(basename "$img")
name="${filename%.*}"
# Create WebP version
cwebp "$img" -q 85 -o "$OUTPUT_DIR/${name}.webp"
# Create thumbnail
convert "$img" -resize 300x300> "$OUTPUT_DIR/${name}-thumb.webp"
echo "Processed: $filename"
fi
done
# Upload processed images
rclone sync "$OUTPUT_DIR" "r2:$BUCKET_NAME/images/processed/" --progress
echo "Processing and upload completed!"
Cost Management
Monitoring Usage
- Check your R2 usage in Cloudflare dashboard
- Set up billing alerts
- Monitor bandwidth usage (though egress is free)
Cost Optimization Tips
- Regularly clean up unused files
- Use appropriate storage classes
- Implement lifecycle policies for temporary files
- Compress files before uploading
Security Considerations
Access Control
Use signed URLs for sensitive content:
javascriptconst signedUrl = await generateSignedUrl(bucketName, objectKey, expireTime)
Implement proper CORS settings:
json{ "AllowedOrigins": ["https://yourdomain.com"], "AllowedMethods": ["GET"], "AllowedHeaders": ["*"], "MaxAgeSeconds": 3000 }
Content Protection
- Use Cloudflare's security features
- Implement hotlink protection
- Consider watermarking for valuable images
Troubleshooting
Common Issues
Files not loading:
- Check bucket permissions
- Verify CORS settings
- Ensure correct URLs
Slow loading:
- Enable Cloudflare caching
- Optimize file sizes
- Use appropriate formats
Upload failures:
- Check API token permissions
- Verify bucket exists
- Check file size limits
Debug Commands
# Test connectivity
rclone ls r2:your-bucket-name
# Check file permissions
curl -I https://your-bucket.r2.dev/test-file.jpg
# Validate configuration
rclone config show
Migration from Other Services
From AWS S3
# Direct sync between services
rclone sync s3:old-bucket r2:new-bucket --progress
From GitHub/Local Storage
# Migrate existing assets
find ./docs/public -name "*.jpg" -o -name "*.png" -o -name "*.mp4" | \
xargs -I {} rclone copy {} r2:your-bucket/migrated/
Conclusion
Cloudflare R2 provides an excellent solution for hosting media files for your VitePress site. With zero egress fees and global CDN integration, it's both cost-effective and performant. The S3 compatibility makes it easy to integrate with existing tools and workflows.
Key benefits:
- ✅ Zero bandwidth costs
- ✅ Global CDN included
- ✅ S3-compatible tools
- ✅ Easy integration with VitePress
- ✅ Scalable and reliable
Start small with a few images and gradually migrate your entire media library as you become more comfortable with the service.