Adaptadores full-stack: Astro, React Router y SvelteKit
Los grandes frameworks full-stack no corren en Workers por arte de magia: un adaptador traduce su servidor a un Worker desplegable. Qué hace un adaptador, cómo se configuran los de Astro, React Router (el sucesor de Remix) y SvelteKit, y por qué todos convergen en el mismo destino de compilación.
Hono es perfecto cuando quieres un router y poco más, pero a menudo llegas al edge con un framework full-stack ya en la mano: Astro para contenido, React Router para aplicaciones o SvelteKit para lo que sea. La buena noticia es que los tres despliegan en Workers, y lo hacen a través de la misma pieza discreta: un adaptador. Entender qué es un adaptador y qué produce es lo que convierte “desplegar mi web en Cloudflare” de un ritual de configuración a un modelo mental claro.
- Definir qué es un adaptador y qué transforma en tiempo de build.
- Configurar el despliegue de Astro en Workers con su adaptador oficial.
- Situar los adaptadores de React Router y SvelteKit y cómo declaran el entorno.
- Reconocer el patrón común: todos compilan al mismo destino, un Worker.
El adaptador: un compilador hacia el Worker
Un framework full-stack tiene, en abstracto, dos mitades: un motor de renderizado que convierte tus componentes en HTML, y una capa de servidor que decide qué renderizar ante cada petición. Esa capa de servidor está escrita de forma neutral, sin comprometerse con ningún entorno concreto. El adaptador es el puente: en tiempo de build toma esa capa neutral y la reescribe para que hable el idioma del destino.
Para Cloudflare, “hablar el idioma del destino” significa una cosa muy concreta que ya conoces: producir un fetch handler. El adaptador no reinventa el framework; envuelve su servidor en la forma que el runtime de Workers sabe invocar, y coloca junto a él los assets estáticos que la web necesita.
npm create cloudflare@latest -- mi-app --framework=astro
Detecta el entorno
El adaptador sabe qué APIs existen en Workers y cuáles no, y adapta el código del servidor a esa superficie concreta.
Empaqueta la salida
Produce el bundle del servidor como un Worker y separa los archivos estáticos, listos para servirse desde el edge.
Expone el entorno
Conecta los bindings de la plataforma —env— al contexto que tu framework entrega a las rutas del servidor.
Astro en Workers
Astro rinde por defecto a HTML estático, pero con el adaptador @astrojs/cloudflare y output: "server" pasa a renderizar bajo demanda dentro de un Worker. La configuración vive en astro.config.mjs y es deliberadamente escueta:
import { defineConfig } from "astro/config";
import cloudflare from "@astrojs/cloudflare";
export default defineConfig({
output: "server",
adapter: cloudflare(),
});
A partir de ahí, cada página .astro que no sea estática se renderiza en el servidor por petición, y los bindings de la plataforma aparecen en Astro.locals.runtime.env. Dentro de una página o de un endpoint de API accedes a tus recursos igual que en un Worker crudo, solo que a través del locals que Astro te entrega:
export async function GET({ locals }) {
const { env } = locals.runtime;
const valor = await env.CACHE.get("clave");
return new Response(valor ?? "sin dato");
}
El adaptador no te obliga a renderizarlo todo en el servidor. Con output: "static" sigues generando un sitio estático; con "server" cada ruta es dinámica por defecto; y puedes marcar páginas concretas como prerenderizadas con export const prerender = true. Esa granularidad es clave en el edge: renderizas en el servidor solo lo que de verdad cambia por petición, y sirves como estático lo que no.
React Router y SvelteKit
El patrón se repite con variaciones de nombre. React Router v7 —el sucesor directo de Remix, con el que se fusionó a finales de 2024— despliega en Workers a través de su plantilla oficial de Cloudflare, apoyada en el plugin de Vite de la plataforma. Sus loader y action reciben un context desde el que alcanzas los bindings en context.cloudflare.env:
export async function loader({ context }: LoaderFunctionArgs) {
const { env } = context.cloudflare;
const { results } = await env.DB.prepare("SELECT * FROM posts").all();
return { posts: results };
}
SvelteKit usa @sveltejs/adapter-cloudflare, y su convención para el entorno es event.platform.env. En un +page.server.ts o en un endpoint, el objeto platform que Cloudflare inyecta trae los bindings, el contexto de ejecución y la caché:
export async function load({ platform }) {
const sesion = await platform.env.KV.get("sesion");
return { sesion };
}
No dejes que la diferencia de nombres te confunda: Astro.locals.runtime.env, context.cloudflare.env y platform.env son tres puertas distintas a exactamente el mismo objeto env de la plataforma. Cada framework elige dónde colgarlo en su propio contexto, pero debajo es el segundo argumento del fetch handler de siempre. Memorizar las tres rutas de acceso es trivial una vez entiendes que todas apuntan al mismo llavero.
El destino común
Cuando das un paso atrás, los tres frameworks cuentan la misma historia. Cada uno tiene su motor de renderizado y su cultura —islas en Astro, loader y action en React Router, load en SvelteKit— pero todos, al pasar por su adaptador de Cloudflare, colapsan en la misma forma de salida: un Worker con un fetch handler más una carpeta de assets estáticos servidos desde el edge.
flowchart TB A[Astro] --> AD1[adaptador cloudflare] R[React Router v7] --> AD2[adaptador cloudflare] S[SvelteKit] --> AD3[adaptador cloudflare] AD1 --> W[Worker con fetch handler] AD2 --> W AD3 --> W W --> AS[Assets estaticos en el edge] style W fill:#fab387,color:#11111b
El plugin de Vite de Cloudflare cierra el círculo en desarrollo: hace que vite dev ejecute tu framework sobre workerd, el runtime real, en lugar de sobre Node. Así el entorno local deja de ser una imitación y pasa a ser el mismo motor que producción, con tus bindings emulados incluidos.
La idea profunda de esta lección es que un adaptador convierte a la plataforma en un destino de compilación, y a tu framework en un frontend de compilador que emite hacia él. Es la misma relación que hay entre un lenguaje y una arquitectura de CPU: escribes en el lenguaje de alto nivel del framework —componentes, loader, islas— y el adaptador traduce eso al lenguaje máquina de la plataforma, que resulta ser el fetch handler y un manifiesto de assets. Esta perspectiva tiene tres consecuencias que valen oro. La primera es que la elección de framework y la elección de dónde desplegar dejan de estar acopladas: mientras exista un adaptador, cualquier framework serio puede aterrizar en Workers sin reescribirse, porque la traducción vive en el adaptador y no en tu código. La segunda es que la razón de que esto sea posible es la misma que atraviesa todo el track: los frameworks modernos se construyen sobre los estándares web —Request, Response, fetch— y Workers implementa esos mismos estándares, así que el “salto semántico” que el adaptador debe cubrir es mínimo. La tercera, y la más liberadora, es que entender el destino te devuelve el control: cuando sabes que tu preciosa app de Astro o de SvelteKit es, al final del build, un Worker corriente, dejas de tratar el despliegue como una caja negra y empiezas a razonar sobre él con las mismas herramientas —logs, bindings, límites, colocación— que usarías con cualquier Worker escrito a mano. El framework te da la ergonomía; el adaptador te da el destino; y tú, si conoces ambos, no dependes de la magia de ninguno.
- Crea un proyecto con
npm create cloudflare@latesteligiendo Astro, y localiza enastro.config.mjsla línea del adaptador y eloutput: "server". - Escribe un endpoint de API que lea un binding desde
locals.runtime.envy devuélvelo en la respuesta. - Compara mentalmente cómo se accede al entorno en Astro, React Router y SvelteKit, y escribe las tres rutas de acceso de memoria.
- Ejecuta el proyecto con el plugin de Vite de Cloudflare y confirma en la salida que el servidor de desarrollo corre sobre
workerd, no sobre Node.