I lost production data on a Tuesday afternoon because wrangler.toml
had one missing field. Not a code bug. Not a logic error. A missing preview_id
.
By default, wrangler dev
uses a local SQLite simulation — safe, isolated, zero real traffic. The moment you add --remote
, every KV read and write goes to the actual Cloudflare namespace over the API. If your wrangler.toml
only has the id
field pointing at your production namespace, those writes land in prod. No warning. No confirmation prompt. Just silent data mutation on the namespace your live users depend on.
The fix is a single extra field:
[[kv_namespaces]]
binding = "MY_STORE"
id = "PROD_NAMESPACE_ID_HERE"
preview_id = "DEV_NAMESPACE_ID_HERE"
Wrangler automatically routes --remote
traffic through preview_id
instead of id
. Create a separate dev namespace with wrangler kv namespace create "MY_STORE_dev"
, drop its ID into preview_id
, and your production namespace is untouched. This should probably be in the quickstart docs. It isn't, at least not prominently.
The second thing worth knowing: --remote
exposes a behavioral gap that local simulation hides entirely. Local KV is synchronous and in-process — a put()
followed by a get()
on the same key always returns the fresh value. Remote KV is eventually consistent. I had a rate-limiting worker that looked completely broken under --remote
: I'd write a counter, immediately read it back, and get the old value. The worker was correct. The local simulation had been lying to me about how production actually behaves. Switching to --remote
(against a dev namespace, not prod) surfaced the real race condition. That's uncomfortable, but it's accurate.
There's also a write-rate ceiling worth knowing before you run any kind of seed script: hit roughly 1,000 writes/minute and you'll start seeing 429 Too Many Requests
with error code 10013
. A 70ms sleep between writes keeps you under the limit without dramatically slowing a seed operation down.
I wrote up the full breakdown — including the wrangler tail
JSON truncation trap that cost me two hours, a shell script for seeding a dev namespace with representative data, and the exact cacheTtl: 0
pattern for honest read behavior — over on dailymanuallab.com.