Cloudflare shipped Worker Previews this week: run npx wrangler preview on a branch and you get a full preview environment — its own code, its own config, its own URL, its own Durable Object namespace, its own Container application. Push again, it redeploys automatically if you’re on Git-connected Workers Builds. On paper it’s the thing every team building on Workers has hand-rolled at some point with suffixed environment names and a prayer.
I went looking for the part that doesn’t get a changelog bullet, and found it in one sentence buried in the docs: a service binding from a Preview still calls the bound Worker’s production deployment. Not the preview of that other Worker. Production. Every time.
What actually gets isolated
Run the command, read what you get:
npx wrangler preview
From a feature branch, this creates a dedicated preview. Cloudflare’s own framing: “a production-like place to run, with its own code, configuration, URL, observability, and state.” The isolation is real for the things that matter most in day-to-day testing:
- Durable Objects — a fresh namespace per preview, created automatically the first time you run the command on that branch
- Containers — same deal, a new Container application per branch
- State and storage — scoped to that branch, so two engineers testing two features don’t stomp on each other’s data
- Observability — traces, logs, errors, metrics, all filterable down to a single preview
You set shared defaults once in wrangler.json and override per-environment without touching production:
{
"vars": { "ENVIRONMENT": "production" },
"previews": {
"vars": { "ENVIRONMENT": "preview" }
}
}
That’s a clean model. It’s also exactly the model every team already tries to build by hand with environment suffixes (api-staging-jsmith, api-pr-4821) and a naming convention that rots the day someone forgets it. Cloudflare just made the convention the platform.
The gap: service bindings still call production
Here’s the sentence that should stop you before you wire Previews into a CI pipeline and call it “full staging”: a service binding declared in a Preview Worker still resolves to the production deployment of the Worker it’s bound to, not that Worker’s own preview. Queues have a milder version of the same problem — a Preview can publish messages, but there’s no isolated consumer yet. Workflows aren’t auto-isolated per branch at all; you configure those separately.
Picture the actual topology most teams running Workers have:
checkout-worker (Preview, branch: add-promo-codes)
└─ service binding → pricing-worker
pricing-worker (production)
You deploy your promo-code branch, you test checkout end to end, everything passes — because pricing-worker in production happily calculated a price with no promo logic at all, since that logic doesn’t exist there yet. Your Preview told you the feature works. It didn’t, not in any environment that reflects what you’re about to ship. The Preview was isolated for exactly the part of the system you were changing, and silently coupled to production for the part you weren’t thinking about.
This is the same failure mode as mocking a downstream service in a unit test and then shipping the mock’s assumptions straight to prod — except it looks like an integration test, not a unit test, so it earns more trust than it should.
Where this actually bites
I’ve watched this exact shape of bug in three different stacks that had nothing to do with Cloudflare: Kubernetes namespaces where a ClusterIP service resolved cross-namespace by accident, Terraform workspaces that shared a remote state backend nobody isolated, docker-compose stacks where a sidecar silently pointed at a prod database URL baked into the base image. Isolation is never a binary “isolated” or “not isolated” property of an environment — it’s isolated per resource type, and the resource type nobody isolates first is the one that connects services to each other.
If your Worker architecture is a single Worker with Durable Objects and KV, Previews give you close to real staging today, for free, per branch. If your architecture is a dozen Workers calling each other through service bindings — which is the architecture Cloudflare’s own docs recommend for anything beyond a toy app — Previews currently validate each Worker in isolation while silently gluing the system back together through production. That’s worse than no staging environment, because no staging environment at least tells you honestly that you’re testing against nothing.
What I’d actually do with this today
- Audit your binding graph before you trust a green Preview. If a Worker under test has a service binding, write down explicitly which side of that call is isolated and which side isn’t. Don’t assume; the docs don’t surface this per-binding in the preview UI, you have to know your own wiring.
- Keep smoke-testing cross-Worker flows in a real staging deploy until Cloudflare ships isolated service bindings for Previews — treat Previews as “fast, safe to test the Worker I’m actually changing,” not “fast, safe to test the system.”
- Flag Queues and Workflows explicitly in your PR template if your branch touches them. “Tested in Preview” means something different depending on which primitive you’re looking at, and that nuance will not survive a standup unless someone writes it down.
Worker Previews solve a real, annoying problem — the one where every engineer on a team invents their own slightly-wrong version of “my own copy of the app to test against.” The feature is good. The one sentence about service bindings is the part that decides whether your team ships a false sense of safety or an actual one, and it’s easy to miss because it reads like a minor caveat instead of the load-bearing fact that it is.