effidevFlutter · Edge de Cloudflare · Optimización de costes en la nube
Español

Migración de Cloudflare Pages a Workers Static Assets

Arquitectura de 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: Reemplazar pages_build_output_dir con assets.directory completa 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.