Migración de Cloudflare Pages a Workers Static Assets

Desde finales de 2025, Cloudflare anunció oficialmente la transición de Pages al modo de mantenimiento (maintenance mode) y declaró que concentrará todas las inversiones futuras y las nuevas funciones en la plataforma Workers. Los proyectos existentes de Pages continuarán funcionando, pero las nuevas características como Cron Triggers, una Observability mejorada y la vinculación directa de Durable Objects solo estarán disponibles en Workers.
Este artículo aborda el procedimiento completo para migrar sin tiempo de inactividad de Cloudflare Pages a Workers Static Assets en sitios estáticos y aplicaciones full-stack. Puesto que este blog (effidev.dev) opera como un proyecto de Pages con pages_build_output_dir: "dist", está escrito desde la perspectiva de llevar a cabo esta misma migración.
Resumen clave
- Pages está en modo mantenimiento: Se han detenido las inversiones en nuevas funciones y optimizaciones. Workers es el método oficial recomendado para servir archivos estáticos y lógica dinámica como una sola unidad de despliegue.
- Solicitudes de activos estáticos gratuitas e ilimitadas: Los archivos estáticos (HTML, CSS, imágenes) servidos a través de Workers Static Assets no cuentan como invocaciones del Worker, por lo que no generan costes.
- Límite de 100.000 archivos: En planes de pago, se admiten hasta 100.000 archivos por versión de Worker y 25 MiB por archivo individual (Wrangler 4.34.0+).
- Cambio de una sola línea en
wrangler.jsonc: Reemplazarpages_build_output_dirconassets.directorycompleta la transición principal.- Integración de adaptadores de framework: En frameworks SSR como Astro, Next.js y SvelteKit, basta con configurar el adaptador compatible con Cloudflare Workers para desplegar los activos estáticos y la lógica del servidor en un solo Worker.
1. Pages vs Workers Static Assets: ¿Qué cambia?
Guía oficial de migración de Cloudflare describe las principales diferencias:
| Criterio de comparación | Cloudflare Pages | Workers Static Assets |
|---|---|---|
| Estado de la plataforma | Modo mantenimiento (sin nuevas funciones) | Desarrollo activo + inversión concentrada |
| Entrega de activos estáticos | Integrado | Mismo soporte mediante la configuración assets.directory |
| Lógica de servidor | _worker.js (limitado) |
Script de Worker completo (vinculaciones ilimitadas) |
| Durable Objects | Sin vinculación directa | Vinculación directa disponible |
| Cron Triggers | No compatible | Compatible |
| Observability | Solo registros básicos | Workers Logs, Tail Workers, Logpush |
| Límite de archivos | 20.000 archivos | 100.000 archivos (Plan de pago) |
| Comando de despliegue | wrangler pages deploy |
wrangler deploy |
| Tarifa de activos estáticos | Gratuito | Gratuito (no es una invocación de Worker) |
La diferencia decisiva reside en la vinculación directa de Durable Objects y Cron Triggers. En Pages, era necesario separarlos en un Worker independiente, mientras que en Workers Static Assets es posible configurar el servicio de activos estáticos + DO + Cron + D1 + R2 dentro de un único archivo wrangler.jsonc.
2. Lista de verificación previa a la migración
Estos son los elementos que debe comprobar antes de realizar la transición. Si no se cumple alguno de ellos, pueden producirse fallos tras la migración.
| Lista de verificación | Método de verificación |
|---|---|
| Versión de Wrangler ≥ 4.34.0 | npx wrangler --version |
| Comprobar directorio de salida del build | ls dist/ o la ruta de salida de build de su framework |
Uso de _worker.js |
Si se usa en modo avanzado de Pages, debe convertirse a un script de Worker |
| Configuración DNS de dominio personalizado | Reconfigurar el registro CNAME del proyecto de Pages a rutas de Workers |
Archivos _headers / _redirects |
No funcionan en Workers → Migrar a la lógica interna del script de Worker |
| Variables de entorno / Secrets | Panel de Pages → [vars] en wrangler.jsonc o wrangler secret put |
# Verificar versión actual de wrangler
npx wrangler --version
# → Debe ser 4.34.0 o superior para admitir el límite de 100.000 archivos
# Comprobar la configuración actual del proyecto Pages
cat wrangler.jsonc
3. Migración principal: Conversión de wrangler.jsonc
Este es el diff con los cambios mínimos para pasar de la configuración existente de Pages a Workers Static Assets.
// wrangler.jsonc
{
"name": "effidev",
"compatibility_date": "2026-08-04",
- "pages_build_output_dir": "dist"
+ "main": "src/worker.ts",
+ "assets": {
+ "directory": "./dist",
+ "binding": "ASSETS"
+ }
}
Con este cambio, el despliegue que anteriormente se realizaba mediante wrangler pages deploy dist pasa a ejecutarse con wrangler deploy. Los activos estáticos se cargan automáticamente desde el directorio ./dist, y el script del Worker (src/worker.ts) procesa la lógica dinámica.
Script del Worker (src/worker.ts)
Este es el punto de entrada más sencillo para un Worker que solo sirve un sitio estático.
// src/worker.ts
// Worker mínimo que solo sirve activos estáticos: sin lógica dinámica, esto es suficiente
export default {
async fetch(
request: Request,
env: { ASSETS: Fetcher },
): Promise<Response> {
// Redirigir todas las solicitudes a la vinculación de activos estáticos
return env.ASSETS.fetch(request);
},
};
Atención a esta trampa: Si establece assets.run_worker_first en true, todas las solicitudes pasarán por el script del Worker y se cobrarán como invocaciones de Worker. Para sitios estáticos, debe mantener el valor predeterminado (false); en este caso, las solicitudes de activos estáticos omiten el Worker y se sirven directamente, resultando gratuitas.
4. Migración de _headers y _redirects al script del Worker
Los archivos _headers y _redirects utilizados en Pages no funcionan en Workers. Se deben gestionar directamente dentro del script del Worker.
// src/worker.ts — Versión con integración de encabezados y redirecciones
export default {
async fetch(
request: Request,
env: { ASSETS: Fetcher },
): Promise<Response> {
const url = new URL(request.url);
// Reemplazo de _redirects: Redirección 301 de patrones de URL antiguos a nuevas rutas
const redirects: Record<string, string> = {
'/old-blog/': '/es/blog/',
'/legacy-page/': '/es/',
};
const redirect = redirects[url.pathname];
if (redirect) {
return Response.redirect(new URL(redirect, request.url).toString(), 301);
}
// Obtener activo estático
const response = await env.ASSETS.fetch(request);
// Reemplazo de _headers: Personalización de encabezados de respuesta
const headers = new Headers(response.headers);
// Añadir encabezados de seguridad
headers.set('X-Content-Type-Options', 'nosniff');
headers.set('X-Frame-Options', 'DENY');
headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
// Política de caché: 1 año para imágenes, 10 minutos para HTML
if (url.pathname.match(/\.(webp|png|jpg|svg|woff2)$/)) {
headers.set('Cache-Control', 'public, max-age=31536000, immutable');
} else if (url.pathname.endsWith('.html') || url.pathname.endsWith('/')) {
headers.set('Cache-Control', 'public, max-age=600, s-maxage=3600');
}
return new Response(response.body, {
status: response.status,
headers,
});
},
};
En este Worker, es necesario establecer assets.run_worker_first en true, ya que se requiere la manipulación de encabezados. Aunque esto genera costes de invocación de Worker, el plan Paid incluye 10 millones de solicitudes al mes por $5, lo cual es suficiente para la mayoría de los sitios estáticos.
// wrangler.jsonc — Habilitar run_worker_first
{
"name": "effidev",
"compatibility_date": "2026-08-04",
"main": "src/worker.ts",
"assets": {
"directory": "./dist",
"binding": "ASSETS",
"run_worker_first": true
}
}
5. Configuración de adaptadores para frameworks SSR (Astro, Next.js, SvelteKit)
Al igual que este blog, un sitio estático que utiliza Astro solo necesita vincular la salida del build a assets.directory sin necesidad de un adaptador adicional. Sin embargo, si utiliza un framework con SSR, requerirá un adaptador compatible con Cloudflare Workers.
Astro (Build estático — El más sencillo)
# astro.config.mjs — Build estático sin adaptador
# Si output: 'static' (predeterminado), el HTML se genera directamente en dist/
npm run build
# → Especificar la carpeta dist/ como assets.directory en wrangler.jsonc
Astro (Modo SSR)
npx astro add cloudflare
// astro.config.mjs — Adaptador SSR para Cloudflare Workers
import cloudflare from '@astrojs/cloudflare';
export default defineConfig({
output: 'server',
adapter: cloudflare({
platformProxy: { enabled: true },
}),
});
Next.js (opennextjs-cloudflare)
npx create-next-app@latest --typescript
npm install @opennextjs/cloudflare
La directiva "use cache" y el streaming RSC abordados en la guía de streaming RSC con "use cache" en Next.js 16 también funcionan correctamente a través del adaptador de Workers.
6. Despliegue integrado de Durable Objects, Cron, D1 y R2
Esta es una característica clave que no era posible en Pages. Al migrar a Workers Static Assets, se puede gestionar tanto el sitio estático como las vinculaciones de backend dentro de un solo archivo wrangler.jsonc.
// wrangler.jsonc — Configuración integrada full-stack
{
"name": "effidev",
"compatibility_date": "2026-08-04",
"main": "src/worker.ts",
"assets": {
"directory": "./dist",
"binding": "ASSETS",
"run_worker_first": true
},
// Base de datos D1
"d1_databases": [
{
"binding": "DB",
"database_name": "effidev-analytics",
"database_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
],
// Almacenamiento de objetos R2
"r2_buckets": [
{
"binding": "BUCKET",
"bucket_name": "effidev-media"
}
],
// Cron Triggers (¡Imposible en Pages!)
"triggers": {
"crons": ["0 */6 * * *"]
}
}
La vinculación de buckets R2 analizada en la guía de migración de AWS S3 a Cloudflare R2 ahora también puede integrarse con el Worker del blog sin necesidad de un Worker independiente.
// src/worker.ts — Manejador integrado de Cron + D1 + R2
interface Env {
ASSETS: Fetcher;
DB: D1Database;
BUCKET: R2Bucket;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
// El Worker procesa directamente los puntos de entrada de la API
if (url.pathname.startsWith('/api/')) {
return handleApi(request, env);
}
// El resto se sirve como activos estáticos
return env.ASSETS.fetch(request);
},
// Cron ejecutado cada 6 horas (¡En Pages se requería un Worker independiente!)
async scheduled(
_controller: ScheduledController,
env: Env,
): Promise<void> {
// Agregar datos analíticos desde D1
const result = await env.DB.prepare(
'SELECT COUNT(*) as total FROM page_views WHERE date = date("now")',
).first();
// Guardar informe diario en R2
await env.BUCKET.put(
`reports/${new Date().toISOString().slice(0, 10)}.json`,
JSON.stringify(result),
);
},
};
async function handleApi(request: Request, env: Env): Promise<Response> {
// Implementación de la lógica de API
return new Response(JSON.stringify({ status: 'ok' }), {
headers: { 'Content-Type': 'application/json' },
});
}
7. Cambio de comandos de despliegue y transición DNS
Comandos de despliegue
# Antes (Pages)
npx wrangler pages deploy dist --project-name=effidev
# Después (Workers Static Assets)
npx wrangler deploy
El despliegue en Workers utiliza el campo name de wrangler.jsonc como nombre del proyecto.
Transición DNS (Dominio personalizado)
El dominio personalizado de un proyecto en Pages apunta mediante CNAME a <project>.pages.dev. Al migrar a Workers, debe conectar el dominio a la ruta (Route) desde Workers en el panel de control de Cloudflare.
| Paso | Acción |
|---|---|
| 1. Despliegue de Workers | Desplegar el Worker y los activos estáticos con wrangler deploy |
| 2. Conectar dominio personalizado | Panel de Cloudflare → Workers → Worker correspondiente → Settings → Domains & Routes → Add Custom Domain |
| 3. Desvincular dominio en Pages | Eliminar el dominio personalizado en el proyecto de Pages (para evitar conflictos DNS) |
| 4. Verificación | `curl -sI https://yourdomain.com/ |
Trampa: Si Pages y Workers se vinculan al mismo dominio al mismo tiempo durante la transición DNS, se producirá un conflicto de enrutamiento. Asegúrese de liberar primero el dominio en Pages antes de agregar la ruta en Workers.
8. Exclusión de archivos innecesarios con .assetsignore
Si existen archivos en el directorio de salida del build que no deben desplegarse, utilice .assetsignore. Tiene la misma sintaxis que .gitignore.
# dist/.assetsignore
_worker.js # Restos del modo avanzado de Pages — no debe cargarse como activo del Worker
*.map # No subir mapas de fuente a producción
.DS_Store
Preguntas frecuentes
¿Debo migrar inmediatamente mi proyecto existente de Pages?
No. Los proyectos existentes en Pages continuarán funcionando. Sin embargo, se recomienda la migración si necesita nuevas funciones como Cron Triggers, vinculación directa de Durable Objects o Workers Logs, o si trabaja en un proyecto a gran escala que supere los 20.000 archivos.
¿Se generan costes de invocación de Worker en un sitio estático?
Si mantiene assets.run_worker_first en su valor predeterminado (false), las solicitudes de activos estáticos no pasan por el Worker, por lo que las tarifas de invocación son 0. Incluso si se establece en true debido a la necesidad de manipular encabezados o lógica de API, el plan Paid ($5/mes) incluye 10 millones de solicitudes, por lo que no se generarán costes adicionales en la mayoría de los sitios estáticos.
¿Qué ocurre con los archivos _headers y _redirects?
No funcionan en Workers. Debe implementar la configuración de encabezados y la lógica de redirección directamente dentro del script del Worker (consulte §4). Al ser gestionados mediante código, esto ofrece la ventaja adicional de facilitar el control de versiones y las pruebas.
¿Cómo migrar si estaba usando Pages Functions (/functions/)?
Integrando cada archivo de Pages Functions en la lógica de enrutamiento del script del Worker. En Workers, se utiliza una estructura que ramifica los manejadores según el patrón de URL desde un único punto de entrada (main). El uso de un framework de enrutamiento ligero como Hono permite mantener un enrutamiento basado en archivos similar a la estructura de Functions previa.