Cloudflare R2 is S3-compatible object storage with no egress fees — ideal for serving encrypted video segments globally. This guide covers the complete R2 setup for BlindCast.
Create an R2 bucket
- Go to Cloudflare Dashboard → R2 → Create bucket
- Choose a bucket name (e.g.,
blindcast-videos)
- Select Automatic location (or choose a region)
Or via Wrangler:
npx wrangler r2 bucket create blindcast-videos
Enable public access
Encrypted segments need to be publicly readable so the player can fetch them. You have two options:
Option A: r2.dev subdomain (simplest)
- In the Cloudflare Dashboard, go to R2 → your bucket → Settings
- Under Public access, enable the r2.dev subdomain
- Your segments are accessible at
https://<bucket>.<accountId>.r2.dev/<key>
Option B: Custom domain (production)
- Add a CNAME record pointing to your R2 bucket
- In the Dashboard, go to R2 → your bucket → Settings → Custom domains
- Add your domain (e.g.,
cdn.example.com)
Create R2 API credentials
The CLI and Worker presign endpoint need S3-compatible credentials:
- Go to R2 → Manage R2 API Tokens → Create API Token
- Select Object Read & Write permission
- Scope to your bucket
- Save the Access Key ID and Secret Access Key
When the Uploader SDK uploads encrypted segments from the browser, it PUTs directly to R2 using presigned URLs. R2 must allow this cross-origin request.
Create a cors.json file:
{
"rules": [
{
"allowed": {
"origins": ["https://your-app.com"],
"methods": ["PUT", "GET", "HEAD"],
"headers": ["Content-Type", "Content-Length"]
},
"exposed_headers": ["ETag"],
"max_age_seconds": 3600
}
]
}
Apply it to your bucket:
npx wrangler r2 bucket cors set blindcast-videos --file cors.json
For local development, add http://localhost:5173 (or your dev server port) to the origins array. Remove it before deploying to production.
Upload with the CLI
The CLI’s upload command works with any S3-compatible storage. Point it at R2 using --endpoint:
blindcast upload ./segments/encrypted/ \
--bucket blindcast-videos \
--endpoint https://<accountId>.r2.cloudflarestorage.com \
--prefix sample-video/
The CLI reads AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY from the environment. Set these to your R2 API credentials:
export AWS_ACCESS_KEY_ID=<your-r2-access-key>
export AWS_SECRET_ACCESS_KEY=<your-r2-secret-key>
See blindcast upload for all options.
Upload from the browser
The Uploader SDK uploads encrypted segments via presigned URLs. Point presignUrl at your Worker’s presign route (see Workers — Presign Endpoint):
import { upload } from "@blindcast/uploader"
const result = await upload(segments, manifest, {
contentId: "my-video-001",
keyServerUrl: "https://your-worker.workers.dev/keys",
presignUrl: "https://your-worker.workers.dev/presign",
})
Caching
R2 serves content with standard HTTP caching headers. For encrypted video:
- Segments (
.ts) — Cache aggressively. Encrypted segments are immutable (content-addressed by key). Set Cache-Control: public, max-age=31536000, immutable.
- Manifests (
.m3u8) — Cache with caution. If you update content, the manifest changes. Use Cache-Control: public, max-age=60 or set appropriate s-maxage for CDN caching.
R2 custom domains automatically integrate with Cloudflare’s CDN. With r2.dev subdomains, caching is handled by R2 directly.
Complete Cloudflare stack
Using R2 with Cloudflare Workers gives you a fully Cloudflare-native BlindCast deployment:
| Component | Service |
|---|
| Key server | Cloudflare Worker (createWorkerKeyServer) |
| Presign endpoint | Same Worker (custom route with aws4fetch) |
| Segment storage | Cloudflare R2 |
| CDN | Cloudflare CDN (via R2 custom domain) |
| Leases (optional) | Cloudflare KV or Durable Objects |