# wrangler dev vs deployed parity

`wrangler dev` runs your Worker in a local workerd with emulated bindings. It is close to production, not identical. The gaps:

## Bindings

- Local dev uses local emulations or preview resources, not your production KV namespace / D1 database / R2 bucket, unless you configure otherwise. Writing test data in dev and expecting it in production (or vice versa) is the classic confusion.
- A binding missing its `id` in config may silently fall back or fail locally while working remotely. Always fill in ids.

## Secrets and vars

- Local secrets come from `.dev.vars` (or `.env`, not both; `.dev.vars` wins and `.env` is ignored when both exist). Production secrets come from `wrangler secret put` / dashboard. A secret present locally but never uploaded is the number one "works locally" failure.
- Use `secrets.required` in config so a missing secret warns in dev instead of 500ing in production.

## Routing

- `wrangler dev` serves on a local URL, not your routes. Route-pattern bugs (wrong specificity, missing route) only appear after deploy. Verify routes with a real deploy to a staging environment, not just dev.
- The Vite plugin path (`vite dev`) selects environments via `CLOUDFLARE_ENV`; plain `wrangler dev` uses `--env`. Mixing the two flags across tools selects different configs silently.

## Errors

- Runtime errors surface in the dev console; in production they surface as error codes (1101 worker threw, 1102 CPU exceeded) in Workers Logs / Tail. Set up log viewing before you need it, not after the first 1101.

## Checklist

- Pre-deploy: secrets uploaded, binding ids present, routes verified on a staging deploy.
- Post-deploy: hit the real URL, check logs, confirm the code path that dev never exercised (cron, queue consumer, WebSocket).