wandres.dev
WRANGLER · el CLI

wrangler dev: el runtime real en local

Desarrollo local con workerd, el mismo runtime que corre en producción: por qué no es un emulador, la orquestación con Miniflare, el estado local en .wrangler/state, la diferencia entre local y remoto con --remote y los remote bindings, y el bucle de hot reload.

⏱ 16 min

Casi todos los entornos de desarrollo local mienten un poco: aproximan la producción con un emulador que se parece, pero no es, el sistema real. wrangler dev rompe esa tradición. Cuando lo ejecutas, tu Worker no corre en una imitación de Workers: corre en workerd, el mismísimo runtime de código abierto que Cloudflare despliega en su red. La consecuencia es profunda —la brecha entre lo que funciona en tu máquina y lo que funciona en el edge casi desaparece—, y para aprovecharla hay que entender qué simula de verdad y qué no.

🎯 Al terminar esta lección sabrás
  • Entender que wrangler dev ejecuta workerd, el runtime real, y no un emulador.
  • Saber dónde vive el estado local y cómo persiste entre sesiones en .wrangler/state.
  • Distinguir el modo local del remoto con --remote y los remote bindings.
  • Manejar el bucle de hot reload y las teclas interactivas del servidor de desarrollo.

workerd en tu máquina, no un emulador

La fidelidad de wrangler dev no es un eslogan: es una consecuencia arquitectónica. Bajo el capó, wrangler arranca workerd —el runtime que Cloudflare liberó como open source y que ejecuta cada Worker en producción— y le entrega tu código. No hay un segundo motor que aproxime el comportamiento del primero; hay un solo motor, corriendo local. Las Web APIs, el modelo de Request y Response, los límites de tiempo de CPU, la forma en que se resuelve un fetch saliente: todo se comporta como en el edge porque es el edge, sin la red global alrededor.

# arranca el runtime local, por defecto en http://localhost:8787
wrangler dev

# fija el puerto y arranca directamente en el navegador
wrangler dev --port 3000

wrangler no invoca workerd a pelo: lo orquesta a través de Miniflare, la capa que prepara el runtime, monta los bindings simulados y expone tanto el servidor local como un inspector compatible con las DevTools de Chrome. Esa orquestación es la que pone un localhost cómodo por encima de un runtime que, por sí solo, solo sabe ejecutar Workers.

Esto colapsa una categoría entera de bugs. El clásico “en mi máquina va” nace casi siempre de que el entorno local y el de producción no son el mismo programa; cuando sí lo son, esa fuente de sorpresas se seca. Ojo con un matiz: los bindings se simulan en local, pero un fetch saliente hacia una API externa viaja de verdad por tu red. El runtime es local; el mundo con el que habla, no siempre.

📝
Local no significa del todo sin conexión

El modo local no toca tus recursos de producción, pero wrangler sí necesita autenticarte la primera vez y puede consultar algún detalle de tu cuenta. Para la iteración pura sobre código y datos locales, en cambio, trabajas de hecho offline: el runtime y los almacenes viven en tu disco, y podrías desconectar la red sin que el bucle se rompa. Distinguir esas dos situaciones evita culpar al modo local de un problema que en realidad era de red.

El estado local: .wrangler/state

Un Worker sin bindings sería un juguete, así que wrangler dev también simula los recursos —pero los simula con fidelidad y de forma local. Un binding de KV, D1 o R2 se respalda por defecto en almacenamiento local dentro de .wrangler/state: D1 es un archivo SQLite real, KV y R2 son almacenes en disco. Ese estado persiste entre ejecuciones, de modo que los datos que escribes en una sesión siguen ahí en la siguiente.

ℹ️
Tus datos locales y los de producción son universos distintos

El almacenamiento local de wrangler dev es tuyo y solo tuyo: vive en .wrangler/state, no toca los recursos reales de tu cuenta y suele ir ignorado por git. Es la propiedad que quieres —experimentas, borras, rompes y repueblas sin miedo a corromper producción—, pero también la que confunde: si esperabas ver en local los datos que hay en tu D1 de producción, no aparecerán, porque son bases distintas. Para hablar con los recursos reales necesitas cruzar deliberadamente al modo remoto.

Puedes redirigir ese estado con --persist-to, apuntando a un directorio propio en vez del .wrangler/state por defecto —útil para versionar un conjunto de datos de prueba o compartirlo entre comandos—. Y empezar de cero es tan simple como borrar la carpeta.

# usar un directorio de estado propio, compartido entre dev y los subcomandos
wrangler dev --persist-to ./.data

# sembrar la base local antes de arrancar
wrangler d1 execute app --local --file ./seed.sql

# empezar de cero: basta con borrar el estado
rm -rf .wrangler/state
💡
`.dev.vars`: secretos solo para tu máquina

Tu Worker en producción recibe secretos cifrados, pero en local no quieres pegar tokens en el manifiesto ni depender de la red. Para eso existe .dev.vars, un archivo con pares clave-valor que wrangler dev inyecta en env solo durante el desarrollo local. Va ignorado por git —contiene credenciales— y es el equivalente local de wrangler secret put: el mismo env.API_TOKEN que en producción sale de un secreto cifrado, en tu máquina sale de este archivo. Así tu código no distingue entornos y tú no filtras nada.

Local frente a remoto

Por defecto, wrangler dev es totalmente local: runtime local, datos locales, cero llamadas a tu cuenta salvo autenticación. El flag --remote invierte la postura: despliega una versión de previsualización en el edge real y la ejecuta contra tus recursos remotos verdaderos. Es más lento y toca infraestructura real, pero es imprescindible cuando dependes de algo que no se puede simular con fidelidad en local.

# todo local: runtime y datos en tu maquina (por defecto)
wrangler dev

# en el edge real, contra tus recursos remotos
wrangler dev --remote

En 2026 existe una tercera vía que difumina la frontera: los remote bindings. Puedes desarrollar con el runtime local y, al mismo tiempo, marcar un binding concreto para que apunte a su recurso remoto real —útil cuando ese recurso es difícil de imitar en local, como Workers AI o un servicio compartido—. Así conservas la velocidad del hot reload local para tu código mientras hablas con la pieza remota que de verdad necesitas.

Y como en el despliegue, wrangler dev --env staging levanta el entorno con nombre que definiste en el manifiesto, con sus propios bindings y variables. Desarrollar contra el entorno equivocado es una fuente silenciosa de confusión —datos que no cuadran, un binding que falta—, así que el flag no sobra: fija contra qué encarnación del Worker estás iterando.

💻

Local (por defecto)

Runtime y datos en tu máquina. Rapidísimo, aislado y sin tocar tu cuenta. El modo del día a día.

🌍

Remoto (--remote)

El Worker corre en el edge real contra tus recursos verdaderos. Más lento, pero fiel a la geografía y a los datos de producción.

🔗

Remote bindings

Runtime local con un binding concreto apuntando a su recurso remoto. Velocidad local donde puedes, realidad remota donde debes.

El bucle de hot reload

El desarrollo con wrangler dev es un ciclo apretado: guardas un archivo, wrangler reempaqueta con esbuild, workerd recarga el isolate y el servidor local sirve la versión nueva en milisegundos. No reinicias nada a mano; el bucle se cierra solo.

flowchart LR
A[Guardas un archivo] --> B[wrangler reempaqueta con esbuild]
B --> C[workerd recarga el isolate]
C --> D[localhost 8787 sirve la version nueva]
D --> A

Mientras corre, el servidor escucha un puñado de teclas que sustituyen a menús y clics:

[b] abrir en el navegador     [d] abrir las devtools
[l] alternar local / remoto   [c] limpiar la consola
[x] salir

La tecla l es la más reveladora: alterna en caliente entre local y remoto sin reiniciar, y notar el cambio de latencia y de datos al pulsarla es la mejor forma de interiorizar qué te da cada modo. La tecla d no abre un panel improvisado, sino las DevTools de Chrome conectadas al inspector de workerd: pones breakpoints, inspeccionas variables y perfilas CPU sobre el runtime real, de modo que depurar un Worker se parece más a depurar una página web que a leer logs de un servidor.

No todo Worker responde a peticiones HTTP. Para uno con un handler scheduled —un cron—, esperar a que llegue la hora sería absurdo, así que wrangler dev expone un disparador local que lanza el evento a demanda con --test-scheduled. Provocas la ejecución programada cuando quieras y depuras su lógica en el mismo bucle apretado que el resto.

# arranca dev habilitando el disparador manual del cron
wrangler dev --test-scheduled

# en otra terminal, dispara el evento scheduled
curl "http://localhost:8787/__scheduled"
💡
Con un framework, el runtime también es real bajo Vite

Si construyes una app full-stack, no ejecutas dos servidores fingidos. El plugin @cloudflare/vite-plugin mete workerd dentro del dev server de Vite, así que tu framework se sirve con hot reload instantáneo y, a la vez, tu código de servidor corre en el runtime auténtico con acceso a los bindings. Es la misma promesa de fidelidad de wrangler dev, integrada en el flujo de un framework moderno en lugar de vivir aparte.

La simetría dev-prod no es comodidad: es corrección

El valor de wrangler dev no está en que sea rápido —que lo es—, sino en que elimina una mentira. Durante décadas, programar para servidores significó desarrollar contra una aproximación: un SQLite fingiendo ser Postgres, un mock fingiendo ser una API, un localhost fingiendo ser un centro de datos. Cada aproximación es una grieta por donde se cuela un bug que solo aparece en producción, justo donde más caro es. Ejecutar workerd en local cierra la grieta más grande de todas —la del propio runtime— porque el motor que valida tu código en tu máquina es, byte a byte, el que lo ejecutará ante millones de usuarios. Lo que queda por vigilar es lo que ese runtime local no puede reproducir por definición: la topología de la red global, la latencia real hacia los datos, el comportamiento de un recurso remoto que solo existe en la nube. Ahí es donde --remote y los remote bindings dejan de ser opciones y pasan a ser parte del método: no basta con que el código sea correcto en el runtime, tiene que serlo también en la geografía. Dominar el desarrollo en el edge es, en el fondo, saber exactamente dónde termina la fidelidad de tu máquina y empieza la del planeta, y cruzar esa frontera a propósito y no por sorpresa.

⚔️ Habita el bucle local
  1. Arranca wrangler dev en un Worker con un binding de KV; escribe una clave desde el handler y confírmala volviendo a leerla tras reiniciar el servidor.
  2. Localiza .wrangler/state en tu proyecto e inspecciona qué se guardó; verifica que esté ignorado por git.
  3. Con el servidor corriendo, cambia el texto de la Response, guarda y mide cuánto tarda el hot reload en reflejarlo.
  4. Pulsa d para abrir las DevTools, pon un breakpoint en el handler y examina el objeto env en vivo.
  5. Pulsa l para alternar a remoto y nota la diferencia de latencia y de datos; razona si un recurso concreto de tu app lo resolverías con --remote o con un remote binding puntual.