Ephem-Cache: Caching en Edge para vistas previas locales con contenido multimedia usando Cloudflare Workers y Quick Tunnels
Elimina la latencia en vistas previas en localhost con Ephem-Cache. Cachea en el borde activos estáticos pesados mediante Cloudflare Workers mientras tunnelizas solicitudes dinámicas de regreso a local.

Quick answer
Ephem-Cache: Acelera vistas previas en localhost con Cloudflare: quick comparison answer
Choose the tunnel tool based on the network model: public HTTPS URLs for webhooks and demos, private mesh access for internal apps, and managed infrastructure when policy controls matter most.
Which tunnel tool is best for public webhook testing?
Use a public HTTPS localhost tunnel with stable URLs. InstaTunnel focuses on webhook testing, demos, OAuth callbacks, and MCP endpoint workflows.
When should I choose a private network tool instead?
Choose a private mesh or Zero Trust tool when every user and service should stay inside a controlled private network.
Introducción: El cuello de botella del ciclo de retroalimentación del frontend remoto
Estás terminando una página de aterrizaje con mucho contenido multimedia. Tiene imágenes WebP de alta resolución, video de fondo en bucle y algunos activos 3D. Todo se siente instantáneo en tu portátil. Inicias tu servidor de desarrollo (Vite, Next.js o similar) y quieres que un diseñador remoto, gerente de producto o cliente lo revisen en vivo.
Lo habitual es usar un túnel. Herramientas como ngrok, Localtunnel y Cloudflare Tunnel (cloudflared) exponen un puerto local en una URL pública. Esto funciona bien para llamadas HTML y API. Pero para medios pesados tiene una debilidad estructural: cada byte de cada activo sale de tu máquina a través de tu enlace de subida en casa u oficina, una vez por espectador, por recarga.
- La subida es el cuello de botella. Si tres revisores abren el enlace a la vez, compiten por el mismo canal upstream. Un video de 45 MB que se transmite sin problemas desde tu SSD se ralentiza al tener que cruzar tu enlace tres veces.
- La vista previa no representa producción. Videos atascados y imágenes lentas hacen que la página parezca peor que el sitio desplegado, por lo que recibes retroalimentación sobre tu velocidad de subida en lugar de tu diseño.
Ephem-Cache es el nombre que uso para un patrón pequeño que soluciona esto. Un Cloudflare Worker se sitúa delante de tu túnel. Permite que Cloudflare cachee los medios estáticos pesados tras la primera recuperación, y reenvía todo lo dinámico (HTML, llamadas API, WebSockets para recarga en caliente) directamente a tu máquina.
Esta guía cubre la arquitectura, un Worker funcional, las configuraciones del servidor de desarrollo que de otra forma rompen la configuración, y los límites y compromisos de seguridad que debes conocer. Los detalles aquí fueron verificados con la documentación actual de Cloudflare a partir del 14 de octubre de 2026. Dejé intencionadamente fuera números de referencia, porque los resultados dependen de tu enlace de subida, tus archivos y las ubicaciones de tus revisores. La sección de medición muestra cómo obtener los tuyos.
1. La Arquitectura
[ Revisor remoto ]
│
▼
[ Borde de Cloudflare: Worker con cache habilitado ]
│
├── Cache HIT ──► servido desde la caché de Cloudflare, Worker no ejecutado
│
└── Cache MISS / solicitud dinámica
│
▼
Worker reenvía a la URL del Quick Tunnel (trycloudflare.com)
│
▼
cloudflared ──► http://localhost:5173
Las reglas de enrutamiento son simples:
- Medios cacheables (imágenes, videos, fuentes, activos 3D) devuelven un encabezado
Cache-Controlque permite a Cloudflare almacenarlos. Tras la primera recuperación, las solicitudes posteriores pueden responderse sin involucrar tu máquina. - Todo lo demás (HTML, rutas API, WebSockets HMR, páginas renderizadas en servidor) se marca como
no-storey pasa en cada solicitud.
¿Por qué cachear con Workers en lugar de usar la API de Caché?
Versiones anteriores de este patrón usaban caches.default (la API de Caché) dentro del Worker. La documentación de Cloudflare ahora describe una función separada, Workers Caching, y encaja mejor con este trabajo:
API de Caché (caches.default) |
Workers Caching | |
|---|---|---|
| Alcance | Solo en un centro de datos. Los contenidos no se replican en otros lugares. | Escalonado: caché de nivel inferior cerca del visitante más una capa superior que agrega llenados en toda la red. |
| Dónde funciona | Documentado como funcional en dominios personalizados (y funciones de páginas). | Sigue al Worker, por lo que funciona en workers.dev, rutas, dominios personalizados y enlaces de servicio. |
| Fallos simultáneos | No se colapsa. Un pico puede invocar tu Worker una vez por solicitud. | Colapsado: muchas solicitudes simultáneas para una clave invocan el Worker una vez por centro de datos. |
| Contenido parcial | cache.put lanza en respuesta 206, por lo que debes cachear toda la respuesta tú mismo. |
Cloudflare elimina Range, almacena el 200 completo y divide rangos por sí mismo. |
| Purga | cache.delete es local a un centro de datos. La purga global pasa por la purga de zona. |
ctx.cache.purge() desde dentro del Worker, con las opciones en la sección 5. |
Estas diferencias importan aquí. El colapso de solicitudes evita que tres revisores disparen tres copias de una carga de 45 MB en la primera carga. La gestión nativa de rangos importa para la reproducción de `video. No necesitar un dominio personalizado elimina un paso de configuración.
Workers Caching requiere Wrangler 4.69.0 o superior.
2. Configuración
Paso 1: Inicia el servidor de desarrollo y un Quick Tunnel
Inicia tu servidor de desarrollo (el puerto predeterminado de Vite es 5173), luego abre un Quick Tunnel en otra terminal:
cloudflared tunnel --url http://localhost:5173
cloudflared imprime una URL aleatoria https://<palabras-aleatorias>.trycloudflare.com. No se necesita cuenta ni dominio de Cloudflare para esta parte.
Conoce los límites de Quick Tunnels antes de basar tu trabajo en ellos. Cloudflare documenta estos:
- Cada túnel maneja hasta 200 solicitudes en curso. Las solicitudes que exceden eso reciben un
429HTTP. - Eventos enviados por servidor no son soportados. WebSockets no están en la lista de funciones no soportadas.
- No hay SLA ni garantía de disponibilidad, y la URL cambia cada vez que reinicias
cloudflared. - Quick Tunnels no son soportados si existe un
config.yamlen tu directorio.cloudflared.
El límite de 200 solicitudes es otra razón para cachear. Los aciertos de caché nunca alcanzan el túnel.
Paso 2: Corrige la verificación del host en tu servidor de desarrollo
Este paso es fácil de olvidar y la causa más común de que la configuración muestre una página en blanco. Cuando el Worker reenvía una solicitud, el encabezado Host es el hostname del túnel, no localhost. Los servidores de desarrollo modernos rechazan hosts desconocidos.
Vite muestra Blocked request. This host is not allowed. Permite el dominio del túnel en vite.config.ts:
import { defineConfig } from "vite";
export default defineConfig({
server: {
// Un punto al principio permite cualquier subdominio de trycloudflare.com.
allowedHosts: [".trycloudflare.com"],
},
});
Evita allowedHosts: true. Esto desactiva la verificación del host completamente, exponiendo tu servidor de desarrollo a ataques de DNS-rebinding. Como alternativa, cloudflared puede reescribir el encabezado con --http-host-header localhost:5173.
Next.js restringe sus endpoints solo para desarrollo, incluyendo el WebSocket HMR en /_next/webpack-hmr, a solicitudes de misma origen. Añade el hostname que usarán tus revisores (el hostname de tu Worker) en allowedDevOrigins en next.config.js. Además, actualiza a Next.js 16.1.7 o superior. CVE-2026-27977 describe un fallo en versiones 16.0.1 a 16.1.6 donde next dev podía aceptar conexiones WebSocket con Origin: null incluso con allowedDevOrigins configurado. Como estás poniendo un servidor de desarrollo en internet público, quieres que eso esté parcheado.
Paso 3: Crea el proyecto Worker
npm create cloudflare@latest ephem-cache-worker
cd ephem-cache-worker
Ahora Cloudflare recomienda wrangler.jsonc en lugar de wrangler.toml para nuevos proyectos. Usa esta configuración:
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "ephem-cache-preview",
"main": "src/index.ts",
"compatibility_date": "2026-10-14",
"cache": { "enabled": true },
"vars": {
"LOCAL_ORIGIN": "https://replace-me.trycloudflare.com"
}
}
cache.enabled hace que Cloudflare verifique su caché antes de ejecutar tu Worker. La compatibility_date es la fecha de hoy, como sugieren los ejemplos de Cloudflare.
Paso 4: El Worker
Reemplaza src/index.ts con lo siguiente. Ha sido revisado en sintaxis, pero pruébalo en tu proyecto antes de confiar en él.
export interface Env {
LOCAL_ORIGIN: string; // por ejemplo, "https://palabras-aleatorias.trycloudflare.com"
PURGE_TOKEN?: string; // secreto opcional para el endpoint de purga
}
// Activos pesados y que cambian poco, que vale la pena cachear durante una revisión.
// JS y CSS se excluyen deliberadamente (ver sección 4).
const MEDIA =
/\.(png|jpe?g|gif|webp|avif|svg|ico|mp4|webm|mov|woff2?|ttf|otf|glb|gltf|ktx2|hdr)$/i;
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise3Response3E {
const url = new URL(request.url);
// Opcional: purga la caché de este Worker a demanda.
if (url.pathname === "/__ephem/purge" NDND request.method === "POST") {
if (!env.PURGE_TOKEN || request.headers.get("x-purge-token") !== env.PURGE_TOKEN) {
return new Response("Forbidden", { status: 403, headers: { "Cache-Control": "no-store" } });
}
const result = await ctx.cache.purge({ purgeEverything: true });
return new Response(result.success ? "Purged" : "Purge failed", {
status: result.success ? 200 : 500,
headers: { "Cache-Control": "no-store" },
});
}
const isUpgrade = request.headers.get("Upgrade")?.toLowerCase() === "websocket";
const isCacheableMedia =
request.method === "GET" NDND !isUpgrade NDND MEDIA.test(url.pathname);
// Reapunta la solicitud al túnel.
const target = new URL(request.url);
const origin = new URL(env.LOCAL_ORIGIN);
target.protocol = origin.protocol;
target.host = origin.host;
const upstreamReq = new Request(target, request);
if (isCacheableMedia) {
// Siempre pide a tu máquina el archivo completo para que la caché almacene
// una respuesta `200` completa, nunca un `304` o respuesta parcial.
upstreamReq.headers.delete("If-None-Match");
upstreamReq.headers.delete("If-Modified-Since");
upstreamReq.headers.delete("Range");
}
let upstream: Response;
try {
upstream = await fetch(upstreamReq);
} catch {
return new Response("Ephem-Cache: no se pudo conectar al túnel local.", {
status: 502,
headers: { "Content-Type": "text/plain", "Cache-Control": "no-store" },
});
}
// WebSocket (HMR) pasa directo.
if (isUpgrade || upstream.status === 101) return upstream;
const res = new Response(upstream.body, upstream);
if (isCacheableMedia NDND upstream.status === 200) {
res.headers.set("Cache-Control", "public, max-age=3600");
} else {
res.headers.set("Cache-Control", "no-store");
}
return res;
},
} satisfies ExportedHandler3CEnv3E;
Aquí hay algunas decisiones de diseño que vale la pena explicar.
Cada respuesta que no sea media se marca explícitamente como no-store. Con Workers Caching habilitado, una respuesta 200 sin encabezado Cache-Control aún se cachea según la heurística de RFC 9111, por 2 horas por defecto. Sin la no-store explícita, tus respuestas HTML y API podrían servirse obsoletas desde la caché.
Las actualizaciones WebSocket se devuelven sin cambios. Cloudflare documenta que las solicitudes de actualización WebSocket bypassan la caché de Workers y siempre invocan tu Worker. Devolver la respuesta de actualización sin modificar permite que la recarga en caliente siga funcionando.
Los encabezados condicionales y de rango se eliminan en la ruta de medios. Los servidores de desarrollo responden a solicitudes condicionales con 304, y un 304 no tiene cuerpo que cachear. Cloudflare también elimina Range antes de invocar el Worker y devuelve la porción solicitada del respuesta completa cacheada, por lo que el Worker nunca tiene que implementar rangos de bytes. Si un Worker devuelve su propio 206, Cloudflare no lo almacenará.
El Worker se mantiene pequeño. Solo reenvía solicitudes y establece encabezados. Tanto los planes gratuitos como los de pago te dan subsolicitudes y tiempo de CPU para esto.
Paso 5: Despliega con la URL del túnel actual
La URL del túnel cambia cada sesión, así que pásala en el despliegue en lugar de editar el archivo:
npx wrangler secret put PURGE_TOKEN # opcional
npx wrangler deploy --var LOCAL_ORIGIN:https://tu-tunel-actual.trycloudflare.com
wrangler deploy acepta --var key:valor como alternativa al bloque vars en tu configuración, y el valor de la bandera prevalece si ambos están definidos. Un efecto secundario útil: por defecto, la versión del Worker forma parte de la clave de caché, así que cada despliegue empieza con una caché fría. Una revisión fresca nunca hereda activos de una anterior.
Envuelve ambos comandos en un script de paquete para que una nueva sesión sea en un solo comando.
3. Cómo funciona la caché
Conocer la clave de caché y las reglas de bypass ahorra horas de confusión:
- La clave es el punto de entrada, la ruta más la cadena de consulta, y (por defecto) la versión del Worker. El host no forma parte de la clave.
?a=1NDNDb=2y?b=2NDNDa=1son claves diferentes. Los encabezados de solicitud comoUser-AgentyCookieno particionan la caché a menos que configuresVary. - Solo
GETyHEADse cachean. Otros métodos siempre llegan a tu Worker. - El bypass automático se aplica. Una respuesta con
Set-Cookie, una solicitud con encabezadoAuthorization(a menos que la respuesta sea explícitamentepublic), o una respuesta marcada comoprivateono-storeno se cachea normalmente. Cf-Cache-Statusindica qué pasó.MISSsignifica que tu Worker se ejecutó y la respuesta se almacenó.HITsignifica que Cloudflare respondió sin ejecutar tu Worker.BYPASSindica que se saltó la caché.- La primera vista no es más rápida. Una solicitud en frío para un video grande debe recorrer todo el camino desde tu máquina. Workers Caching almacena toda la respuesta, así que el primer visitante paga por todo el archivo. Calienta la caché tú mismo abriendo la página una vez antes de que comience la revisión.
- Se aplican límites de tamaño. La página de límites de Workers indica que los límites de la CDN se aplican: 512 MB por objeto en planes Free, Pro y Business, y 5 GB en Enterprise. Los activos mayores no se cachearán.
4. Invalidación: qué cachear y por cuánto tiempo
Los archivos locales cambian constantemente, así que el riesgo principal son activos obsoletos. La estrategia:
- Cachea medios, no código. Los archivos fuente en un servidor de desarrollo mantienen nombres estables mientras cambian sus contenidos. Vite maneja bien esto haciendo que las solicitudes de módulos fuente sean condicionales (
304), mientras las dependencias preempaquetadas tienenmax-age=31536000,immutableen el navegador. Poner.jsy.cssdetrás de una caché de borde de una hora solo generaría confusión, así que la regex anterior los excluye. - Mantén el TTL corto. Una hora es un punto de partida. Reduce
max-agesi los diseñadores intercambian imágenes durante la revisión. - Purge bajo demanda. El endpoint opcional en el Worker llama a
ctx.cache.purge({ purgeEverything: true }):curl -X POST https://<tu-worker-host>/__ephem/purge -H "x-purge-token: $PURGE_TOKEN"
purge también acepta tags y pathPrefixes. Workers Caching es privado para tu Worker, y las purgas a nivel de zona desde el panel o API no lo afectan. Las purgas usan los límites de tasa de la capa gratuita independientemente de tu plan.
4. Re-despliega para resetear. Como la versión del Worker forma parte de la clave, wrangler deploy es en sí una invalidación completa.
5. Previsualiza una versión de producción para revisiones finales. Si ejecutas vite build y sirves la salida con vite preview, los nombres de archivos llevan hashes de contenido, así que los archivos modificados obtienen URLs nuevas automáticamente. En ese momento, puedes agregar con seguridad js|css|mjs a la regex.
5. Midiendo el resultado tú mismo
En lugar de citar números que dependen de una configuración específica, mide los tuyos:
# 1. Primera solicitud: espera Cf-Cache-Status: MISS
curl -sI https://<tu-worker-host>/media/hero.mp4 | grep -i cf-cache-status
# 2. Segunda solicitud: espera HIT
curl -sI https://<tu-worker-host>/media/hero.mp4 | grep -i cf-cache-status
# 3. Compara el tiempo de descarga con la URL del túnel directamente
curl -s -o /dev/null -w "worker: %{time_total}s %{size_download} bytes\n" https://<tu-worker-host>/media/hero.mp4
curl -s -o /dev/null -w "túnel: %{time_total}s %{size_download} bytes\n" https://<tu-tunel>.trycloudflare.com/media/hero.mp4
Para una imagen realista, ejecuta la misma comparación desde una máquina en la región de tus revisores, o pide a un revisor que abra la pestaña de Red en DevTools y comparta un HAR. Observa tu enlace de subida mientras dos o tres personas cargan la página. La versión cacheada debería dejarlo casi inactivo después de la primera recuperación.
6. Límites, términos y costos
- Plan gratuito de Workers: 100,000 solicitudes por día, 10 ms de CPU por solicitud y 50 subsolicitudes por solicitud. Un proxy de streaming usa poca CPU. Pero con cache habilitado, cada solicitud al Worker, incluyendo aciertos, se factura a la tarifa estándar.
- Términos de Cloudflare sobre archivos grandes. El blog de Cloudflare explica que sus términos cambiaron para que videos y otros archivos grandes puedan servirse a través de su CDN cuando están alojados en servicios como Stream, Images o R2. Los videos y archivos grandes alojados fuera de Cloudflare permanecen restringidos en la CDN. Un túnel a tu portátil es un origen externo. Revisa los términos específicos de servicio de Cloudflare antes de enrutar videos grandes, y limítalo a sesiones cortas de revisión. Para medios permanentes, aloja los archivos en R2.
- Sin SLA. Quick Tunnels son una herramienta de prueba. Para algo en lo que dependas, usa un Cloudflare Tunnel nombrado.
7. Consideraciones de seguridad
Este patrón pone un sitio no publicado en internet público, así que algunos puntos requieren cuidado.
- Las respuestas cacheadas omiten tu Worker. Cualquier verificación que hagas dentro del Worker (un token, una contraseña) solo se ejecuta en fallos de caché. Una vez que un archivo multimedia está cacheado, cualquiera que conozca su URL puede acceder a él. No uses este patrón para activos confidenciales o en embargo.
- La URL del túnel también es pública. Quien conozca la dirección aleatoria
trycloudflare.compuede acceder directamente a tu servidor de desarrollo, saltándose el Worker. La documentación de Cloudflare describe una opción--allowed-mailpara Quick Tunnels que requiere que los visitantes ingresen un PIN de una sola vez. Esa puerta también desafía las solicitudes del Worker, así que no combina con esta arquitectura. Una alternativa práctica es que el Worker agregue un encabezado secreto a sus solicitudes upstream y rechace las solicitudes sin él en un pequeño plugin de Vite:// vite.config.ts (extracto) const guard = { name: "ephem-guard", configureServer(server) { server.middlewares.use((req, res, next) =3e { if (req.headers["x-ephem-secret"] !== process.env.EPHEM_SECRET) { res.statusCode = 403; return res.end("Forbidden"); } next(); }); }, };
Esto solo cubre solicitudes HTTP simples. Mantén el secreto fuera de tu repositorio.
- No filtrar secretos en bundles. Todo en código del cliente, incluyendo variables de entorno empaquetadas por tu framework, termina en archivos que los visitantes pueden leer. Mantén URLs de bases de datos y claves privadas en el servidor.
- Mantén listas de hosts permitidos estrechas. Usa
.trycloudflare.comen lugar de desactivar las verificaciones de host. - Derriba después de la revisión. Detén
cloudflared, elimina el Worker (npx wrangler delete) o al menos purga su caché, y rotaPURGE_TOKENsi lo compartiste.
Conclusión
Un túnel local es la forma más rápida de mostrar trabajo sin terminar, pero hace que tu conexión de subida sea la red de entrega para cada imagen y video. Poner un pequeño Worker de Cloudflare con Workers Caching delante del túnel traslada el tráfico repetido de medios pesados a la caché de Cloudflare y deja tu máquina para servir solo la primera solicitud de cada archivo más el tráfico dinámico que debe provenir de ella.
El patrón tiene advertencias reales: la primera vista sigue siendo lenta, los archivos cacheados son efectivamente públicos, Quick Tunnels no garantizan disponibilidad, y los términos de Cloudflare limitan archivos grandes alojados externamente en su CDN. Dentro de esos límites, facilita revisiones remotas de páginas con mucho contenido multimedia mucho menos dependientes de tu velocidad de subida. Comienza con una sesión corta, revisa Cf-Cache-Status en algunos activos, y mide la diferencia en tus propios archivos.
Fuentes
- Documentación de Cloudflare: Quick Tunnels
- Documentación de Cloudflare: Visión general de Workers Cache
- Documentación de Cloudflare: Configuración de Workers Cache
- Documentación de Cloudflare: Claves de cache de Workers
- Documentación de Cloudflare: Purga e invalidación de la caché
- Documentación de Cloudflare: Limitaciones de Workers Cache
- Documentación de Cloudflare: API de Caché
- Documentación de Cloudflare: Cómo funciona la caché
- Documentación de Cloudflare: Límites de Workers
- Documentación de Cloudflare: Comandos de Wrangler
- Documentación de Cloudflare: Configuración de Wrangler
- Blog de Cloudflare: Adiós, sección 2.8 y hola a los nuevos términos de servicio
- Vite: Preempaquetado de dependencias
- Vite: Por qué Vite
- Quasar: Abrir un servidor de desarrollo al público (guía de allowedHosts)
- Parallel Works: marco de trabajo de túneles para servidores de desarrollo (Next.js allowedDevOrigins)
- CVE-2026-27977 (verificación de origen en WebSocket de HMR en Next.js)
Related InstaTunnel pages
Continue from this article into the most relevant product guides and workflows.
Related Topics
Keep building with InstaTunnel
Read the docs for implementation details or compare plans before you ship.