Cloudflare Pages + R2

Static site hosting and object storage for the podcast.

Goals

Host the podcast feed and episode pages (Pages) and serve audio files (R2) without managing servers. The combination gives us a CDN-backed static site with audio storage on the same platform, at effectively zero cost for our traffic volume.

Effectiveness

Works well once wrangler is configured. Deploys are fast, the R2 presigned URLs are stable, and Pages caches globally without configuration. Zero operational overhead once set up.

What made it effective

Bonus utility

wrangler r2 object put <bucket>/<key> --file <path> is usable without any project configuration — just a Cloudflare API token. Useful for one-off file uploads from scripts.

Friction / pain points / surprises

npx wrangler is not available in environments without npm/npx. In a Docker container built with Bun only, npx doesn't exist. All wrangler invocations must use bunx wrangler instead. This caused two consecutive pipeline failures before all npx calls were replaced globally.

wrangler skips uploading files it considers unchanged, based on content hash. If dist/ contains a file identical (by hash) to what's already deployed, wrangler uploads nothing. This caused a stale deploy where the audio player HTML changes weren't reflected on the live site — the dist/ being deployed was from a previous failed run and matched the old deployment. Fix: always regenerate dist/ from scratch before deploying, or verify the deployed content matches expectations after deploy.

Pages deployment propagation isn't instant. After wrangler pages deploy exits, the new content may not be live at the public URL for 30–60 seconds. Checking the URL immediately after deploy can return stale cached content, which looks like a deploy failure.

R2 bucket must be set to public explicitly. A new R2 bucket defaults to private. Audio files uploaded before enabling public access are inaccessible at the .r2.dev URL with no meaningful error — just a 403. Enable public access in the Cloudflare dashboard before the first upload.

CF_API_TOKEN must have Pages and R2 permissions separately. A token scoped only to Pages can't write to R2, and vice versa. The deploy pipeline needs both. A token created from the "Edit Cloudflare Workers" template covers neither; create a custom token with Cloudflare Pages:Edit and Workers R2 Storage:Edit.