# R2 Custom Domain Setup Guide This guide walks through setting up a production-ready custom domain for the Sojorn R2 bucket. ## Why Custom Domain? The R2 public development URL (`https://pub-*.r2.dev`) has significant limitations: - ⚠️ Rate limited - not suitable for production traffic - ⚠️ No Cloudflare features (Access, Caching, Analytics) - ⚠️ No custom SSL certificates - ⚠️ Not recommended by Cloudflare for production A custom domain provides: - ✅ Unlimited bandwidth and requests - ✅ Full Cloudflare features (caching, CDN, DDoS protection) - ✅ Custom SSL certificates - ✅ Professional appearance - ✅ Better SEO and branding ## Recommended Domain Structure If your main domain is `sojorn.com`, use a subdomain for media: - `media.sojorn.com` - Professional, clear purpose - `cdn.sojorn.com` - Common CDN pattern - `images.sojorn.com` - Descriptive alternative **Recommended**: `media.sojorn.com` ## Setup Steps ### Step 1: Connect Domain to R2 Bucket 1. **Go to Cloudflare Dashboard** → **R2** → **`sojorn-media`** bucket 2. Click **"Settings"** tab 3. Under **"Public Access"**, click **"Connect Domain"** 4. Enter your chosen subdomain: `media.sojorn.com` 5. Click **"Connect Domain"** Cloudflare will automatically: - Create a DNS CNAME record pointing to R2 - Provision an SSL certificate - Enable CDN caching ### Step 2: Verify Domain Configuration Wait 1-2 minutes for DNS propagation, then test: ```bash # Check DNS record nslookup media.sojorn.com # Test direct access (upload a test file first) curl -I https://media.sojorn.com/test-image.jpg # Should return: HTTP/2 200 ``` ### Step 3: Configure Environment Variable Set the public URL in Supabase secrets: ```bash # Set the R2 public URL secret npx supabase secrets set R2_PUBLIC_URL=https://media.sojorn.com --project-ref zwkihedetedlatyvplyz # Verify all R2 secrets are set npx supabase secrets list --project-ref zwkihedetedlatyvplyz ``` Expected secrets: - `R2_ACCOUNT_ID` - Your Cloudflare account ID - `R2_ACCESS_KEY` - R2 API token access key - `R2_SECRET_KEY` - R2 API token secret key - `R2_PUBLIC_URL` - Your custom domain URL (e.g., https://media.sojorn.com) ### Step 4: Deploy Updated Edge Function The edge function now uses the `R2_PUBLIC_URL` environment variable: ```bash # Deploy the updated edge function npx supabase functions deploy upload-image --project-ref zwkihedetedlatyvplyz # Verify deployment npx supabase functions list --project-ref zwkihedetedlatyvplyz ``` ### Step 5: Test End-to-End 1. **Upload a test image** through the app 2. **Check the database** for the generated URL: ```sql SELECT id, image_url, created_at FROM posts WHERE image_url IS NOT NULL ORDER BY created_at DESC LIMIT 1; ``` 3. **Verify URL format**: Should be `https://media.sojorn.com/{uuid}.{ext}` 4. **Test in browser**: Open the URL directly - image should load 5. **Check in app**: Image should display in the feed ## Troubleshooting ### Domain Not Connecting **Error**: "Failed to connect domain" **Solution**: - Verify domain is managed by Cloudflare (same account) - Check domain isn't already connected to another R2 bucket - Ensure subdomain doesn't have conflicting DNS records ### SSL Certificate Issues **Error**: "SSL handshake failed" or "NET::ERR_CERT_COMMON_NAME_INVALID" **Solution**: - Wait 5-10 minutes for SSL certificate provisioning - Verify domain shows "Active" status in R2 bucket settings - Check Cloudflare SSL/TLS mode is set to "Full" or "Full (strict)" ### Images Return 404 **Error**: Images uploaded but return 404 on custom domain **Solution**: - Verify domain connection is "Active" in R2 settings - Check file actually exists: `curl -I https://{ACCOUNT_ID}.r2.cloudflarestorage.com/sojorn-media/{filename}` - Verify bucket name matches in edge function (should be `sojorn-media`) ### Old Dev URLs Still Used **Problem**: New uploads use dev URL instead of custom domain **Solution**: - Verify `R2_PUBLIC_URL` secret is set: `npx supabase secrets list` - Redeploy edge function: `npx supabase functions deploy upload-image` - Check edge function logs for errors: `npx supabase functions logs upload-image` ## Cloudflare Caching Configuration After connecting the domain, optimize caching in Cloudflare Dashboard: 1. **Go to** your domain in Cloudflare Dashboard 2. **Navigate to** Rules → Page Rules 3. **Create a rule** for `media.sojorn.com/*`: - Cache Level: Standard - Edge Cache TTL: 1 month - Browser Cache TTL: 1 hour This ensures images are cached at Cloudflare's edge for fast global delivery. ## Security Considerations ### CORS Configuration The R2 bucket CORS is already configured for all origins (`*`): ```json { "Allowed Origins": "*", "Allowed Methods": ["GET", "HEAD", "PUT"], "Allowed Headers": "*" } ``` For production, consider restricting origins: ```json { "Allowed Origins": ["https://sojorn.com", "https://app.sojorn.com"], "Allowed Methods": ["GET", "HEAD"], "Allowed Headers": ["*"] } ``` ### Access Control Images are publicly readable by design. To restrict access: 1. Use signed URLs (requires code changes) 2. Implement Cloudflare Access rules 3. Add authentication checks before generating upload URLs ## Cost Considerations ### R2 Pricing (as of 2026) - **Storage**: $0.015/GB per month - **Class A Operations** (writes): $4.50 per million requests - **Class B Operations** (reads): $0.36 per million requests - **Data Transfer**: FREE (no egress fees) ### Custom Domain Benefits - **Cloudflare CDN**: Free caching reduces R2 read operations - **No Egress Fees**: Unlike AWS S3, R2 doesn't charge for bandwidth - **Edge Caching**: Reduces origin requests by 95%+ ## Monitoring Track R2 usage in Cloudflare Dashboard: 1. **Go to** R2 → `sojorn-media` bucket 2. **Check** Metrics tab for: - Storage size - Request count - Bandwidth usage Set up alerts for: - Storage exceeding threshold (e.g., 10GB) - Unusual request spikes - Error rate increases --- ## Quick Reference ### Required Supabase Secrets ```bash R2_ACCOUNT_ID= R2_ACCESS_KEY= R2_SECRET_KEY= R2_PUBLIC_URL=https://media.sojorn.com ``` ### Deploy Commands ```bash # Set secret npx supabase secrets set R2_PUBLIC_URL=https://media.sojorn.com --project-ref zwkihedetedlatyvplyz # Deploy function npx supabase functions deploy upload-image --project-ref zwkihedetedlatyvplyz # View logs npx supabase functions logs upload-image --project-ref zwkihedetedlatyvplyz ``` ### Test Commands ```bash # Check DNS nslookup media.sojorn.com # Test HTTPS curl -I https://media.sojorn.com/ # Upload test file curl -X PUT "https://.r2.cloudflarestorage.com/sojorn-media/test.txt" \ -H "Authorization: Bearer " \ --data "test content" # Verify via custom domain curl -I https://media.sojorn.com/test.txt ``` --- **Next Steps**: After completing this setup, proceed to the main [IMAGE_UPLOAD_IMPLEMENTATION.md](./IMAGE_UPLOAD_IMPLEMENTATION.md) guide for testing the full upload flow.