TL;DR — Resumen Rápido
Cloudflare Workers ejecuta JavaScript en el edge con aislamientos V8. Despliega tu primer Worker con Wrangler, KV, secretos y CI/CD con GitHub Actions.
Cloudflare Workers lleva la computación serverless al edge — tu código se ejecuta en centros de datos distribuidos en más de 300 ciudades del mundo, a milisegundos de tus usuarios, sin necesidad de aprovisionar ni escalar servidores. Construido sobre aislamientos V8 (el mismo motor que impulsa Chrome y Node.js), Workers logra tiempos de arranque en frío menores a 1 milisegundo, siendo dramáticamente más rápido que las funciones serverless tradicionales basadas en contenedores. En esta guía irás de cero a un Worker completamente desplegado en producción con almacenamiento KV, secretos cifrados, dominios personalizados y un pipeline de despliegue automatizado con GitHub Actions.
Requisitos Previos
- Node.js 18+ instalado localmente
- Una cuenta de Cloudflare (el nivel gratuito funciona para todo en esta guía)
- Familiaridad básica con JavaScript o TypeScript
- Un dominio registrado añadido a Cloudflare (requerido solo para la sección de dominio personalizado)
- npm o pnpm como gestor de paquetes
Creando tu Primer Worker
Instala Wrangler, la CLI oficial de Cloudflare, globalmente:
npm install -g wrangler
wrangler login
El comando wrangler login abre una ventana del navegador y solicita que autorices a Wrangler a acceder a tu cuenta de Cloudflare. Tras la autorización, las credenciales se almacenan en ~/.wrangler/config/default.toml.
Crea un nuevo proyecto:
wrangler init my-api-worker
cd my-api-worker
Wrangler presenta algunas preguntas — elige TypeScript y la plantilla “Hello World” Worker. La estructura del directorio resultante es:
my-api-worker/
src/
index.ts ← tu código Worker
wrangler.toml ← configuración del proyecto
package.json
tsconfig.json
Entendiendo wrangler.toml
El archivo wrangler.toml es el manifiesto del proyecto. Una configuración mínima luce así:
name = "my-api-worker"
main = "src/index.ts"
compatibility_date = "2024-09-23"
[[routes]]
pattern = "api.example.com/*"
zone_name = "example.com"
Campos clave:
| Campo | Propósito |
|---|---|
name | Nombre del Worker mostrado en el panel |
main | Archivo de entrada resuelto por Wrangler |
compatibility_date | Fija el comportamiento de la API del runtime a una fecha específica |
routes | Mapea patrones de URL a este Worker |
[[kv_namespaces]] | Vincula namespaces KV como variables de entorno |
[vars] | Variables de entorno en texto plano |
La compatibility_date es importante — Cloudflare ocasionalmente introduce cambios que rompen la compatibilidad en las APIs de Worker, y esta fecha fija qué conjunto de APIs ve tu Worker. Siempre establécela en la fecha de creación del proyecto y actualízala explícitamente tras revisar el registro de cambios.
Escribiendo el Worker
El punto de entrada del Worker exporta un objeto predeterminado con un manejador fetch. Cada solicitud HTTP entrante llama a este manejador:
export interface Env {
MY_KV: KVNamespace;
API_SECRET: string;
}
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const url = new URL(request.url);
// Ruta: retornar JSON
if (url.pathname === '/api/status') {
return Response.json({ status: 'ok', region: request.cf?.colo });
}
// Ruta: retornar HTML
if (url.pathname === '/') {
return new Response(
`<h1>¡Hola desde el edge!</h1>`,
{ headers: { 'Content-Type': 'text/html;charset=UTF-8' } }
);
}
return new Response('No Encontrado', { status: 404 });
}
};
Patrones de Request y Response
Workers usa la API Fetch estándar — Request, Response y Headers son idénticos a las APIs del navegador. Esto significa que el código que escribes para Workers es en gran parte portable.
Retornar JSON es idiomático con Response.json():
return Response.json({ items: ['a', 'b', 'c'] }, {
headers: { 'Cache-Control': 'public, max-age=60' }
});
Redirigir una solicitud:
return Response.redirect('https://example.com/nueva-ruta', 301);
Modificar una respuesta proxiada (el patrón “transform”):
const upstream = await fetch(request);
const body = await upstream.text();
return new Response(body.replace('texto antiguo', 'texto nuevo'), upstream);
Desarrollo Local
Ejecuta el servidor de desarrollo local:
wrangler dev
Wrangler inicia un servidor local en http://localhost:8787 que emula el runtime de Cloudflare incluyendo KV, Durable Objects, R2 y el objeto de metadatos request.cf. La recarga en caliente se activa automáticamente ante cambios en los archivos.
Para probar contra la infraestructura real de Cloudflare (útil para Workers que llaman a otros servicios de Cloudflare):
wrangler dev --remote
Inspecciona el tráfico en vivo con el log tail integrado en una segunda terminal:
wrangler tail
wrangler tail transmite logs en tiempo real del Worker de producción, mostrando detalles de solicitud/respuesta, salida de consola y excepciones.
Desplegando a Producción
Despliega con un solo comando:
wrangler deploy
Wrangler compila tu TypeScript, empaqueta las dependencias y sube el Worker a la red de Cloudflare. El despliegue se propaga globalmente en segundos. La salida incluye la URL de workers.dev:
Published my-api-worker (2.45 sec)
https://my-api-worker.tu-subdominio.workers.dev
Conectando un Dominio Personalizado
Agrega un bloque routes en wrangler.toml para enrutar solicitudes de tu dominio proxiado por Cloudflare al Worker:
[[routes]]
pattern = "api.example.com/*"
zone_name = "example.com"
Alternativamente, usa la función Custom Domains (sin necesidad de patrón de ruta):
[[routes]]
pattern = "api.example.com"
custom_domain = true
Custom Domains provisiona automáticamente un certificado TLS y maneja todo el enrutamiento.
Variables de Entorno y Secretos
Variables Simples
La configuración no sensible vive en wrangler.toml:
[vars]
ENVIRONMENT = "production"
MAX_RETRIES = "3"
Accede a ellas en el Worker como env.ENVIRONMENT y env.MAX_RETRIES.
Secretos Cifrados
Los secretos se cifran en reposo y nunca son visibles tras la carga. Agrégalos mediante la CLI:
wrangler secret put API_KEY
Wrangler te pedirá que ingreses el valor de forma interactiva. Lista los secretos existentes:
wrangler secret list
En el Worker, los secretos aparecen como env.API_KEY — indistinguibles de las variables simples en tiempo de ejecución pero almacenados cifrados en la bóveda de Cloudflare.
Almacenamiento KV
KV (Workers KV) es un almacén clave-valor distribuido globalmente con consistencia eventual. Destaca para almacenar configuración, sesiones de usuario, respuestas de API en caché y banderas de características.
Crea un namespace KV:
wrangler kv namespace create CACHE
Wrangler devuelve el ID del namespace. Agrega el binding a wrangler.toml:
[[kv_namespaces]]
binding = "CACHE"
id = "abc123def456..."
Escribe y lee desde KV en el Worker:
// Escribir (con TTL opcional en segundos)
await env.CACHE.put('usuario:123', JSON.stringify(datosUsuario), { expirationTtl: 3600 });
// Leer
const raw = await env.CACHE.get('usuario:123');
const usuario = raw ? JSON.parse(raw) : null;
// Eliminar
await env.CACHE.delete('usuario:123');
Comparativa
| Característica | Cloudflare Workers | AWS Lambda@Edge | Deno Deploy |
|---|---|---|---|
| Runtime | Aislamientos V8 | Contenedor Node.js | Aislamientos V8 |
| Arranque en frío | < 1 ms | 100–500 ms | ~50 ms |
| PoPs globales | 300+ | ~4 regiones CloudFront | 35 regiones |
| Nivel gratuito | 100k req/día | Pago por solicitud | 100k req/día |
| Tiempo CPU máx. | 50 ms (gratis) / 30 s (pago) | 30 s | 50 ms |
| Almacenamiento | KV, R2, D1, Durable Objects | DynamoDB (separado) | Deno KV |
| TypeScript | Nativo (integrado) | Mediante paso de build | Nativo |
| Precio (más del gratis) | $0,50/millón solicitudes | ~$0,60/millón + Lambda | $0,50/millón solicitudes |
Workers gana en arranque en frío y distribución global. Lambda@Edge es la elección correcta si ya estás profundamente integrado en el ecosistema AWS. Deno Deploy es una buena alternativa si quieres el modelo de permisos de Deno.
Escenario del Mundo Real: Worker Proxy de API
Tienes una API de terceros que no soporta CORS, requiere una clave de API que no puedes exponer al navegador, y responde con datos que quieres cachear y transformar. Un Cloudflare Worker es la solución perfecta.
export interface Env {
UPSTREAM_API_KEY: string;
CACHE: KVNamespace;
}
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const url = new URL(request.url);
const upstreamUrl = `https://api.terceros.com${url.pathname}${url.search}`;
const cacheKey = upstreamUrl;
// Verificar caché KV primero
const cached = await env.CACHE.get(cacheKey);
if (cached) {
return Response.json(JSON.parse(cached), {
headers: { 'X-Cache': 'HIT', 'Access-Control-Allow-Origin': '*' }
});
}
// Obtener del upstream con clave secreta
const upstream = await fetch(upstreamUrl, {
headers: { 'Authorization': `Bearer ${env.UPSTREAM_API_KEY}` }
});
if (!upstream.ok) {
return new Response('Error upstream', { status: upstream.status });
}
const data = await upstream.json();
// Cachear la respuesta por 5 minutos
ctx.waitUntil(env.CACHE.put(cacheKey, JSON.stringify(data), { expirationTtl: 300 }));
return Response.json(data, {
headers: { 'X-Cache': 'MISS', 'Access-Control-Allow-Origin': '*' }
});
}
};
Este Worker mantiene la clave de API en el servidor, agrega encabezados CORS que la API original no tiene, y sirve respuestas cacheadas desde las ubicaciones edge más cercanas a cada usuario.
CI/CD con GitHub Actions
Automatiza los despliegues en cada push a main:
# .github/workflows/deploy.yml
name: Deploy Worker
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
name: Deploy
steps:
- uses: actions/checkout@v4
- name: Configurar Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- name: Instalar dependencias
run: npm ci
- name: Desplegar a Cloudflare Workers
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
Agrega CF_API_TOKEN y CLOUDFLARE_ACCOUNT_ID como secretos del repositorio en GitHub. Crea el token de API en el panel de Cloudflare bajo Perfil → Tokens de API → Crear Token usando la plantilla “Edit Cloudflare Workers”.
Errores Comunes y Casos Especiales
Límite de tiempo CPU — El plan gratuito permite 10 ms de tiempo CPU por solicitud. El plan Workers Paid sube esto a 30 segundos. El trabajo intensivo en CPU puede alcanzar este límite; descarga los cálculos pesados a una cola o usa Durable Objects.
Límites de sub-solicitudes — Cada invocación de Worker puede hacer hasta 50 llamadas fetch() salientes en el plan gratuito (1000 en el de pago).
Fecha de compatibilidad y cambios que rompen la compatibilidad — Al actualizar compatibility_date, revisa el registro de cambios de las banderas de compatibilidad. Algunos indicadores cambian cómo funcionan Request.clone(), los streams o el manejo de errores.
Límites de tamaño — Los scripts Worker están limitados a 1 MB tras la compresión (10 MB en el plan de pago). Las dependencias npm grandes pueden superar este límite; usa tree shaking y evita empaquetar módulos exclusivos del lado del servidor.
Sin acceso al sistema de archivos — Los Workers no tienen E/S de disco. Toda la persistencia debe ir a través de KV, R2, D1 o Durable Objects.
waitUntil para trabajo en segundo plano — Usa ctx.waitUntil(promise) para trabajo que debe completarse después de enviar la respuesta (como escrituras en caché).
Solución de Problemas
Error: Script startup exceeded CPU time limit — Tu Worker está realizando trabajo costoso durante la inicialización del módulo (en el nivel superior, fuera del manejador). Mueve las operaciones costosas dentro del manejador o usa inicialización lazy.
TypeError: Cannot read properties of undefined (reading 'get') — Un binding KV u otro binding falta en wrangler.toml, o estás accediendo a env.MI_BINDING antes de que el binding esté configurado.
wrangler deploy falla con “Authentication error” — Tu sesión de Wrangler ha expirado. Ejecuta wrangler login nuevamente o establece la variable de entorno CLOUDFLARE_API_TOKEN para entornos CI.
Errores CORS en el navegador — La respuesta del Worker no tiene Access-Control-Allow-Origin. Agrega el encabezado en tu constructor de Response o usa un helper. También maneja la solicitud de preflight OPTIONS por separado.
El dominio personalizado no enruta al Worker — Asegúrate de que el dominio esté proxiado a través de Cloudflare (nube naranja en la configuración DNS), el patrón de ruta en wrangler.toml usa comodín /* si es necesario, y has vuelto a desplegar después de cambiar wrangler.toml.
Resumen
- Cloudflare Workers son funciones serverless basadas en aislamientos V8 que corren en el edge con arranques en frío por debajo del milisegundo y distribución global
- Instala Wrangler con
npm install -g wrangler, crea el scaffold conwrangler inity prueba localmente conwrangler dev - El archivo
wrangler.tomlcontrola el nombre del Worker, el punto de entrada, la fecha de compatibilidad, las rutas y todos los bindings de recursos - Usa
wrangler secret putpara valores sensibles y[vars]enwrangler.tomlpara configuración no sensible - Los namespaces KV ofrecen almacenamiento clave-valor distribuido globalmente, accesible mediante
env.BINDING.get/put/delete - Workers supera a Lambda@Edge en arranque en frío y alcance global; Lambda@Edge es preferible para integración profunda con el ecosistema AWS
- La Action
wrangler-actionde GitHub proporciona CI/CD listo para usar; delimita tu token de API solo a permisos de Workers - Vigila los límites de tiempo CPU, los topes de sub-solicitudes y el límite de 1 MB de tamaño de script en el plan gratuito