Why R2?
- S3-Compatible API - Works with AWS SDK clients
- Zero Egress Fees - No charges for downloads
- Fast Global CDN - Low latency worldwide
- Generous Free Tier - 10 GB storage, 1M writes, 10M reads per month
Prerequisites
- Cloudflare account (sign up free)
- Payment method on file (required even for free tier)
Quick Setup
Create R2 bucket
Navigate to R2 in Cloudflare Dashboard:
- Click Create bucket
- Enter bucket name:
resonance-app(or your preferred name) - Choose location: Automatic (recommended)
- Click Create bucket
Bucket names must be globally unique and DNS-compliant (lowercase, hyphens only).
Generate API token
Create API credentials for programmatic access:
- Go to R2 → Manage R2 API Tokens
- Click Create API token
- Configure permissions:
- Token name:
resonance-api-token - Permissions: Object Read & Write
- Bucket: Select your bucket (
resonance-app)
- Token name:
- Click Create API Token
- Copy the displayed credentials:
- Access Key ID
- Secret Access Key
Get account ID
Find your Cloudflare Account ID:
- In the dashboard sidebar, note your Account ID
- Or copy from the R2 bucket overview page
ea63931e6e8ff54c5be60feacd3026d6R2 Client Configuration
Resonance uses the AWS SDK S3 client with R2’s S3-compatible endpoint.Client Setup
src/lib/r2.ts
src/lib/r2.ts:10.
Key differences from S3:
region: "auto"- R2 handles region automatically- Endpoint uses account ID:
{accountId}.r2.cloudflarestorage.com - No region-specific endpoints needed
Helper Functions
uploadAudio()
uploadAudio()
Upload audio files to R2 with metadata.Parameters:
src/lib/r2.ts
buffer- Audio file as Bufferkey- Object path (e.g.,voices/custom/abc123)contentType- MIME type (default:audio/wav)
src/lib/r2.ts:25.deleteAudio()
deleteAudio()
Delete audio files from R2.Defined in
src/lib/r2.ts
src/lib/r2.ts:40.getSignedAudioUrl()
getSignedAudioUrl()
Generate temporary presigned URLs for secure audio access.Returns: Temporary URL valid for 1 hourDefined in
src/lib/r2.ts
Presigned URLs allow secure, time-limited access without exposing API credentials to clients.
src/lib/r2.ts:49.Bucket Structure
Resonance organizes audio files using a consistent key structure:Key Patterns
System voices:voices/system/clxyz123abc
Custom voices:
voices/custom/clabc789def
Generations:
generations/clgen456xyz
All IDs are CUIDs generated by Prisma (
@default(cuid())) defined in prisma/schema.prisma:37 and prisma/schema.prisma:58.Modal Integration
The Chatterbox TTS deployment on Modal mounts the R2 bucket read-only for direct voice reference access.Modal R2 Mount
chatterbox_tts.py
chatterbox_tts.py:22.
Benefits:
- No file duplication between Next.js and Modal
- Voice references are available immediately after upload
- Lower storage costs (single copy in R2)
Voice Path Resolution
When generating TTS, Modal reads voice audio directly from the mounted R2 bucket:chatterbox_tts.py
chatterbox_tts.py:116.
Usage Examples
Upload Custom Voice
Generate Presigned URL
src/app/api/audio/[generationId]/route.ts:1.
Delete Voice and Audio
CORS Configuration
If you need to access R2 files directly from the browser (not recommended for audio), configure CORS:Public Bucket (Optional)
To serve files publicly without presigned URLs:The default setup uses private buckets with presigned URLs for security. Only enable public access if you understand the implications.
Monitoring and Costs
View Usage
- Go to R2 → Overview
- View metrics:
- Storage used (GB)
- Read/write operations
- Egress (always $0 with R2)
Free Tier Limits
| Resource | Free Tier | Overage Cost |
|---|---|---|
| Storage | 10 GB | $0.015/GB/month |
| Class A Operations (writes) | 1M/month | $4.50/million |
| Class B Operations (reads) | 10M/month | $0.36/million |
| Egress | Unlimited | $0 |
Resonance generates ~100 KB audio files. With 10 GB free storage, you can store approximately 100,000 generations before exceeding the free tier.
Troubleshooting
Upload fails with access denied
Modal can’t read voices
- Verify Modal secret
cloudflare-r2is configured with correct credentials - Check
R2_ACCOUNT_IDandR2_BUCKET_NAMEmatch inchatterbox_tts.py:23 - Ensure voice was uploaded successfully to R2
- Test file existence:
Presigned URLs return 403
Presigned URLs have a 1-hour expiration. If URLs are stale, regenerate them by fetching the resource again.Account ID not found
The Account ID is different from your Cloudflare user ID. Find it:- Dashboard sidebar under account name
- R2 → Bucket overview page
- URL path:
dash.cloudflare.com/:account_id/r2
Related Documentation
Modal Deployment
Configure R2 mount in Modal
Database Setup
Store R2 keys in Prisma
Environment Variables
R2 credential configuration