Most staging setups mirror production: a separate database, separate object storage, separate everything. It's the safe approach. It's also expensive and time-consuming to maintain in sync.
This site takes a different approach: staging runs a separate Worker at a different URL, but it shares the same KV namespace, R2 bucket, and D1 database as production. One set of infrastructure, two deployment targets.
Here's what that looks like, why it works for this use case, and where it would break down.
The Setup
Two GitHub Actions workflows:
deploy.yml— triggers on push tomain, deploys todwsa-mm.comdeploy-staging.yml— triggers on push todev, deploys todwsa-staging.4f4p42vhzf.workers.dev
Both workflows run wrangler deploy with the same wrangler.toml. Same bindings, same environment variables, different route. The staging worker reads from the same KV namespace as production.
toml[[kv_namespaces]]
binding = "POSTS_KV"
id = "af2bcae978ce4fb4bec7f9ae325b11bf"
There's no id_staging override. Both environments use the production namespace.
Why This Works for a Blog
The data in this system is append-mostly. Posts are created, rarely updated, never deleted. KV keys like post:my-slug don't change once a post is published. Reaction counts increment but don't need to be isolated from production.
Reading production data in staging is a feature, not a bug. Staging tests against real content: real post titles, real hero images, real view counts. If the staging worker renders a post incorrectly, I see the actual post I'd see in production, not a placeholder.
This is especially useful for layout testing. A post with a very long title breaks layouts differently than a short one. Staging with real data catches this immediately.
The Risk: Staging Mutations Affect Production
This is the real tradeoff. Any write operation that staging performs goes to production data.
The operations that write:
- View counter increments (D1 upsert)
- AI summary generation (KV put)
- Reaction updates (KV put)
- Analytics data points (Analytics Engine write)
Every time I visit a post on staging to test a change, the view counter increments in production. The D1 database doesn't know which worker made the request.
For view counts, this is acceptable — a few extra increments from testing don't meaningfully affect the numbers. For reactions, it would be more visible if I accidentally clicked reaction buttons during testing.
The mitigation I use: be deliberate about interactions on staging. Don't load-test staging (it would inflate production view counts). Test layout and behavior, not database operations.
Where Isolation Would Matter
If this site had:
- User accounts — creating test users on staging would pollute the production user table
- Transactional data — test purchases or orders appearing in production reports
- Email sending — test emails going to real users
- Rate limiting — test traffic consuming production rate limit quotas
...I'd need separate infrastructure. The shared approach only works because the data mutations here are low-stakes.
The Practical Benefits
No sync problem. A separate staging KV namespace would need to be populated with production data to test meaningfully. That's a script to write and maintain. Sharing the namespace eliminates the problem.
Cost. A second KV namespace for staging would cost nothing (Cloudflare's free tier is generous). But a second D1 database, a second R2 bucket, and duplicated worker costs add up for paid features. Shared infrastructure keeps costs linear with usage, not with the number of environments.
No "works on staging" failures from stale data. If staging has production data, a feature that works on staging will work on production.
The Dev Branch Workflow
The flow in practice:
- Make changes on the
devbranch - Push to
dev→ staging auto-deploys in ~1 minute - Test at the staging URL
- Open a PR from
devtomain→ merge → production deploys in ~30 seconds
The gap between staging deploy and production deploy is short enough that data drift between the two environments is negligible.
For most changes — CSS tweaks, new routes, worker logic — the shared data model is an advantage. I test against real content and ship with confidence.