Skip to content

Build1 publisher3 min readPublished Updated

Laravel's session cookie is why your HTML never gets cached at the edge

A dev.to guide moves cacheability rules out of the Cloudflare dashboard and into a Laravel middleware, then strips Set-Cookie from anonymous HTML so a shared cache will actually store it.

The Engineer · Build desk

Drafted by a language model from the sources cited here and checked against its claim ledger before publication. How we use AISend a correction

What happened

  • A guide titled "Full-Page Edge Caching for Laravel Behind Cloudflare: A Practical Guide" was published on dev.to, describing how the authors solved full-page edge caching in production on web-pioneer.com, including mistakes made along the way.
  • The origin-side component is a small middleware the authors call EdgeCache, which is the only place that knows whether a given response is safe to share between visitors.
  • The guide states one design decision that everything else hangs on: cacheability logic lives in the Laravel application, not in the Cloudflare dashboard, and the rules ship with the codebase so nobody has to remember that a dashboard toggle exists.
  • Laravel touches the session on nearly every web request and answers with a Set-Cookie header, and any well-behaved shared cache refuses to store a response that sets a cookie.
  • As a result, teams cache images and JavaScript, leave every HTML response marked DYNAMIC, and wonder why TTFB from abroad is still 600 ms.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

A guide published on dev.to by the team behind web-pioneer.com describes a small Laravel middleware, called EdgeCache, that decides per response whether Cloudflare is allowed to store a page, and strips the Set-Cookie header on the responses it clears [1][2]. The interesting part is not the caching but the location of the decision: the rules ship with the application code rather than living as a toggle in the Cloudflare dashboard [3].

The blocker is mundane. According to the guide, Laravel touches the session on nearly every web request and answers with a Set-Cookie header, and any well-behaved shared cache refuses to store a response that sets a cookie [4]. The result is teams who cache images and JavaScript, leave every HTML response marked DYNAMIC, and then wonder why time to first byte from abroad is still around 600 ms [5].

So Cloudflare is reduced to one instruction: cache HTML when the origin says it is allowed to [6]. The origin is the only component that knows whether a response is safe to share between visitors, and in production the middleware sets Cache-Control: public, max-age=0, s-maxage=600 when it is, and no-cache, private when it is not [7][8]. The allowlist is explicit: GET only, status 200 only, Content-Type containing text/html, no authenticated user, and no flashed session data in _flash.new [9]. Everything else is uncacheable by default, so a new route, an error page or an authenticated area fails safe [10]. Because the rules go through code review and are covered by tests, staging behaves like production [11].

The header split is deliberate. s-maxage applies only to shared caches, so the edge holds the page for 600 seconds, ten minutes, while browsers revalidate on every visit [12][13]. The guide's reasoning is operational rather than aesthetic: a stale page at the edge can be purged centrally in seconds, and a stale page sitting in ten thousand browser caches cannot [14].

Then the line that does the real work. The middleware removes Set-Cookie before setting the cacheable header, because a cookie on a shared-cache response either blocks caching or, worse, hands one visitor's session to everyone [15]. The guide argues this is safe only because the guards run first: by the time the cookie is discarded, the response has been proven to be an anonymous 200 HTML page with no flashed state, so what is thrown away is a throwaway guest session that nothing depends on [16]. That assertion is the load-bearing one. If anything on your anonymous pages depends on guest session continuity, the guards are what has to be extended, not the strip.

Ordering matters too: the middleware is registered at the end of the web group so that it sees the fully built response with sessions attached [17]. On the Cloudflare side the guide calls for exactly one Cache Rule, matching the hostname and marking the response eligible for cache [18].

What to watch is the flash guard, since it is the only check that depends on reading per-visitor state rather than on request shape, and any feature that writes to the session on a GET will silently make pages uncacheable. Note also that this is one team's account of one site [1], and that the published excerpt offers no before-and-after measurements beyond the author's ranking of full-page HTML caching above OPcache tuning, query fixes, Redis fragment caching and queue offloading [19]. The excerpt breaks off mid-sentence in the Cloudflare configuration section [20].

Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories