Full-page edge caching de Laravel con Cloudflare

En pocas palabras: Cloudflare cachea el HTML completo de Laravel en su borde y lo sirve desde el data center más cercano al visitante. web-pioneer.com lo aplicó para bajar un TTFB que rondaba los 600ms desde regiones lejanas; el bloqueo son las cookies de sesión que Laravel manda en cada respuesta.

El cloudflare full-page edge caching sirve el HTML ya terminado desde un data center cercano al visitante, sin que tu servidor Laravel se entere del pedido. El equipo de web-pioneer.com lo puso en producción para atacar un TTFB que desde países lejanos rondaba los 600ms. Ninguna otra optimización que probaron llegó a ese impacto.

El caché de borde (edge caching) es la técnica de guardar la respuesta HTML completa en los servidores de Cloudflare distribuidos por el mundo, de modo que un visitante recibe la página desde el nodo más próximo en milisegundos. En Laravel casi nadie lo usa porque el framework toca la sesión en cada request y manda cookies, y un caché compartido bien portado se niega a guardar una respuesta con cookie.

En 30 segundos

  • Impacto real: web-pioneer.com atacó el TTFB de 600ms desde regiones lejanas cacheando el HTML en el borde.
  • El bloqueo: Laravel manda Cache-Control: no-cache, private y un Set-Cookie de sesión en casi cada respuesta, y Cloudflare no cachea nada con cookie.
  • La arquitectura: la app decide qué es cacheable (un middleware), Cloudflare solo obedece. Nada de configurar la lógica en el dashboard.
  • La clave: las rutas públicas van en un grupo sin sesión ni CSRF y emiten Cache-Control: public, s-maxage=600.
  • Del lado Cloudflare: una sola Cache Rule con el Edge TTL en “respect existing headers” para que respete el s-maxage que manda tu servidor.

Cloudflare es una plataforma de CDN y seguridad web desarrollada por Cloudflare Inc., que proporciona caching en el borde, protección DDoS y aceleración de rendimiento para sitios web.

¿Qué es el caché de borde y por qué pega tan fuerte en Laravel?

El caché de borde guarda el HTML final en un data center de Cloudflare cerca del visitante y se lo entrega en milisegundos, sin llegar a tu origin. La diferencia con el resto es de fondo: OPcache, arreglar queries, fragmentos en Redis o mandar trabajo a colas recortan una parte del request, pero el request igual viaja hasta tu servidor, levanta el framework y renderiza la vista.

El edge caching borra el request entero. El servidor ni se entera.

Ponele que tenés visitantes en Argentina, México y España pegándole a un servidor en Europa. Sin caché de borde, cada uno de esos pedidos cruza el océano, arranca Laravel desde cero y arma la página. Con caché de borde, la primera visita paga ese costo y las siguientes se llevan el HTML del nodo más cercano. Por eso el equipo de web-pioneer.com dice que, de todo lo que shippearon para clientes Laravel, esto fue lo que más movió la aguja. Y sin embargo casi nadie lo hace. ¿Por qué? La respuesta está en cómo Laravel maneja la sesión. Relacionado: variables vs secretos en Cloudflare.

¿Por qué Laravel rechaza el caché de página completa?

Laravel rechaza el caché de borde porque toca la sesión en casi cada request web y responde con un header Set-Cookie, y cualquier caché compartido que se precie se niega a guardar una respuesta que setea una cookie. Esa cookie es, por definición, específica de un visitante. Guardarla y servírsela a otro sería un agujero de seguridad.

Encima, la respuesta por defecto de una ruta Laravel viene con Cache-Control: no-cache, private. Traducido: “no compartas esto con nadie”. Cloudflare lee ese header y obedece.

Acá viene el error más común. Muchos activan “Cache Everything” en Cloudflare pensando que con eso alcanza. No alcanza. Si el origen sigue diciendo no-cache y mandando un Set-Cookie, la regla del dashboard pelea contra tu propio servidor. El resultado es impredecible y, en el peor caso, terminás cacheando la sesión de un usuario y sirviéndosela a todos. El tema es que el origen y el borde tienen que estar de acuerdo, no darse órdenes contradictorias.

¿Cómo se arma la arquitectura ganadora: la app decide, el borde obedece?

La arquitectura correcta pone toda la lógica de cacheabilidad en la aplicación Laravel, no en el dashboard de Cloudflare. Hay una sola decisión de diseño de la que cuelga todo lo demás: el origen decide, el borde obedece. En evitar caídas con DNS correcto profundizamos sobre esto.

Cloudflare queda reducido a una instrucción tonta y única: “cacheá el HTML cuando el origen diga que se puede”. El origen (un middleware chico, en web-pioneer.com lo llamaron CacheableResponse) es el único lugar que sabe si una respuesta dada es segura para compartir entre visitantes.

La ventaja de este modelo es que la decisión vive al lado del código que conoce el contexto. Una página de producto pública, sin usuario logueado, es cacheable. El carrito de compras, no. El middleware evalúa eso por ruta y emite el header correcto: Cache-Control: public, s-maxage=600 cuando se puede compartir, y no-cache cuando no. Vos escribís la regla una vez, en un lugar, y Cloudflare la respeta en todo el mundo.

¿Cómo controlar los headers Cache-Control con un middleware?

El middleware emite Cache-Control: public, s-maxage=600 en las rutas públicas para autorizar el caché de borde durante 600 segundos. La distinción que tenés que tener clarísima es la de las dos directivas de tiempo:

  • s-maxage: cuánto tiempo el caché compartido (Cloudflare) puede servir la respuesta. Es el que manda en el borde.
  • max-age: cuánto tiempo el navegador del visitante puede guardar la respuesta en su caché local.

Con s-maxage=600 le decís a Cloudflare “guardá esto 10 minutos”. Podés combinarlo con un max-age más corto o directamente cero si querés que el navegador siempre revalide contra el borde. Laravel trae de fábrica el middleware cache.headers, que te deja setear estas directivas por ruta sin escribir código propio, aunque para lógica condicional (¿el usuario está logueado?) te conviene tu propio middleware. Sobre eso hablamos en benchmarking de Containers vs MicroVMs.

¿Cómo separar las rutas públicas de la sesión y el CSRF?

Las rutas cacheables no pueden tocar la sesión ni el token CSRF, así que van en un grupo de middleware separado, sin StartSession ni VerifyCsrfToken. Esto es lo que la mayoría se saltea y por eso les falla.

Poner public en el Cache-Control no sirve de nada si la respuesta igual arrastra un Set-Cookie. Y mientras la ruta pase por el middleware de sesión de Laravel, ese Set-Cookie se genera solo, aunque no lo quieras. La solución es armar un grupo de rutas públicas que no incluya la sesión: sin cookie de sesión, sin token CSRF, sin nada específico del visitante.

Subís el middleware, separás las rutas, probás en local, funciona bárbaro, lo mandás a producción y de golpe descubrís que una vista compartida seguía llamando a csrf_token() en un formulario, lo que reactivaba la sesión y volvía a inyectar la cookie que tanto trabajo te costó sacar. Por eso conviene auditar las vistas de las rutas públicas: cualquier @csrf, cualquier session(), cualquier helper que toque el usuario rompe el caché en silencio.

¿Cómo configurar Cloudflare para que respete tus decisiones?

Del lado de Cloudflare necesitás exactamente una Cache Rule: que matchee tu hostname, ponga la respuesta como cacheable (Cloudflare no cachea HTML por defecto) y —esto es lo que importa— tenga el Edge TTL en “respect existing headers”, no en “override”. Con eso, Cloudflare deja de imponer su propia lógica y pasa a obedecer al s-maxage de tu origen. Para más detalles técnicos, mirá R2 como alternativa a S3.

La regla es no usar “Cache Everything” a lo bruto si querés que el origen sea quien decide. Cuando el Edge TTL respeta los headers existentes, Cloudflare lee el s-maxage=600 de tu respuesta y cachea exactamente eso, exactamente ese tiempo. El TTL mínimo recomendado según la implementación de web-pioneer.com es de 600 segundos, un equilibrio razonable entre frescura del contenido y tasa de aciertos (cache hit).

Para invalidar el caché cuando publicás contenido nuevo, tenés la purga por API de Cloudflare: le pegás al endpoint de purge con la URL específica y el borde la vuelve a pedir al origen en el próximo request. Si tu infraestructura vive en un hosting como donweb.com con Cloudflare por delante, este flujo de purga selectiva es el que te mantiene el HTML fresco sin bajar la performance.

Comparación: qué optimiza cada técnica en Laravel

TécnicaQué recortaEl request llega al originImpacto relativo
OPcache tuningTiempo de compilar PHPMedio
Fixes de queriesTiempo de base de datosMedio
Fragmentos en RedisRender parcial de vistasMedio-alto
Colas / queue offloadingTrabajo pesado fuera del requestVariable
Full-page edge cachingEl request enteroNoMáximo (elimina el request entero)
cloudflare full-page edge caching diagrama explicativo

Errores comunes al implementar full page cache en Laravel

  • Confiar en “Cache Everything” del dashboard: si el origen sigue mandando no-cache y Set-Cookie, la regla de Cloudflare pelea contra tu servidor. Sacá primero la cookie en el origen.
  • Dejar la sesión en rutas públicas: mientras la ruta pase por StartSession, se genera el Set-Cookie y Cloudflare no guarda nada. Armá un grupo de middleware sin sesión.
  • Un @csrf escondido en la vista pública: reactiva la sesión y rompe el caché sin avisar. Auditá cada vista cacheable buscando helpers que toquen al usuario.
  • No planear la invalidación: si cacheás 600 segundos pero no purgás al publicar, el visitante ve contenido viejo hasta 10 minutos. Conectá la purga por API de Cloudflare a tus eventos de publicación.

Preguntas Frecuentes

¿Qué diferencia hay entre caché de navegador y caché de borde?

El caché de navegador guarda la respuesta en el dispositivo de un único visitante y lo controla la directiva max-age. El caché de borde la guarda en los data centers de Cloudflare y la comparte entre todos los visitantes, controlado por s-maxage. El de borde acelera la primera visita de cada persona; el de navegador solo las visitas repetidas de la misma.

¿Cuánto mejora la velocidad con edge caching en Laravel?

En la implementación de web-pioneer.com, el TTFB desde países lejanos venía siendo de 600ms. El caché de borde ataca justamente eso: el request deja de viajar al origin, arrancar el framework y renderizar, y el HTML sale del nodo Cloudflare más cercano en milisegundos. El equipo afirma que ninguna otra técnica (OPcache, Redis, fixes de query) se le acercó en impacto.

¿Por qué Laravel no cachea en borde de forma automática?

Porque Laravel toca la sesión en casi cada request web y responde con Cache-Control: no-cache, private más un Set-Cookie. Cualquier caché compartido se niega a guardar una respuesta con cookie, ya que esa cookie es específica de un visitante. Hay que sacar activamente la sesión de las rutas públicas para habilitar el caché.

¿Cómo se invalida el caché de Cloudflare en Laravel?

Con la API de purga de Cloudflare: le pegás al endpoint de purge con la URL específica y el borde vuelve a pedirla al origen en el próximo request. Conviene dispararla desde los eventos de publicación o actualización de tu modelo, para que el contenido nuevo reemplace al cacheado sin esperar a que expire el s-maxage.

¿Sirve el full page cache para páginas con usuarios logueados?

No para el contenido personalizado de un usuario logueado, porque esa respuesta es específica de esa persona y no se puede compartir. El full-page edge caching aplica a rutas públicas sin sesión: home, páginas de producto, notas de blog, landings. El contenido dinámico por usuario se sirve desde el origin o se arma en el cliente sobre un cascarón cacheado.

Conclusión

Lo que cambia con el caché de página completa en el borde es que dejás de optimizar el request y directamente lo eliminás. El resto de las técnicas te ahorran milisegundos dentro de un pedido que igual llega al servidor; esta se los ahorra todos.

El camino concreto: armá un middleware que decida la cacheabilidad, sacá la sesión y el CSRF de las rutas públicas, emití Cache-Control: public, s-maxage=600, creá una Cache Rule en Cloudflare con el Edge TTL en “respect existing headers” y conectá la purga por API a tus publicaciones. Empezá por una sola ruta pública de mucho tráfico, medí el TTFB antes y después, y recién ahí escalá al resto. Si arrancabas de los 600ms de web-pioneer.com, el margen de mejora es enorme sin tocar una línea de tu lógica de negocio.

Fuentes

Te puede interesar...