Sites on which Blitz was installed between the 7th of April and the 26th of August 2024 sent incorrect Cache-Control headers that could cause pages to be stored in a visitor’s local browser cache. Keep reading for the full details of this issue, or jump straight to the sections that explain what this means and whether it affects you.
In April of 2024, a change was made to the Blitz code base that was intended to simplify the Cache-Control header that the plugin sends along with cached pages, without changing its functionality.
Before:
Cache-Control: public, s-maxage=31536000, max-age=0
After:
Cache-Control: public, max-age=31536000
This seemingly innocent change intended to simplify the directive that “only public cache stores may cache pages for 365 days”. As it turns out, the public
directive doesn’t restrict caching to public stores only, meaning that browsers can end up caching pages locally as well.
This is not what should happen. Pages should only be cached in public (shared) stores, otherwise changes to content may not appear for visitors whose browsers have cached pages locally.
How did this happen? #
While cleaning up legacy code during the development of Blitz 5, I reviewed the Cache-Control HTTP header specification and determined that the default cache-control
directive in Blitz could be shortened to public, max-age=31536000
. This was based on the MDN docs which state that:
The
public
response directive indicates that the response can be stored in a shared cache.
I incorrectly interpreted this to mean “in a shared cache only”, and therefore decided to replace what I believed to be a redundant directive with a terser version.
Neither my automated tests nor my manual tests caught this, since tests run on an existing server and the issue only affected new installs. (I’ve since added automated tests to ensure this can never happen again.)
I made a mistake, and I feel terrible about it.
What does this mean? #
This means that cached pages on affected sites may be cached locally in web browsers. Fortunately, only a subset of sites using Blitz are affected, and browsers are not aggressive when it comes to caching HTML (like they are with images, style sheets and other static assets). For example, refreshing a page in the browser sends a request to the server rather than serving it from the local cache.
What sites does it affect? #
This issue only affects sites on which Blitz was installed between the 7th of April and the 26th of August 2024 (fresh installs – not updates – of versions 4.15.0 – 4.22.0 and 5.0.0 – 5.6.4). Since only the default values were changed, it does not affect sites on which Blitz was initially installed prior to version 4.15.0. The issue was fixed in 4.23.0 and 5.7.0.
Additionally, it should not affect sites that use server rewrites to serve cached pages (without a reverse proxy CDN serving cached pages).
To check whether the issue affects your site, enter the URL of a cached page below.
You can also verify whether the issue affects your site by visiting any cached page and inspecting the value of the Cache-Control
response header in the Network tab of your browser’s dev tools. If it exists and has the value public, max-age=31536000
then the issue affects you.
You can additionally check the existing value of the cacheControlHeader
setting under the Loaded Project Config Data section in Utilities → Project Config. If it has the value public, max-age=31536000
then the issue affects you.
What should I do if my site is affected? #
You should first update Blitz to at least version 4.23.0 or 5.7.0. If you are unable to update the plugin, you can override the default values using config settings. Copy the config.php file to craft/config
as blitz.php
(if it doesn’t already exist) and uncomment these lines so that they read:
// The value to send in the cache control header for cached pages.
'cacheControlHeader' => 'public, s-maxage=31536000, max-age=0',
// The value to send in the cache control header for expired pages.
'cacheControlHeaderExpired' => 'public, s-maxage=5, max-age=0',
If using a reverse proxy CDN to cache pages, you should refresh the entire cache after updating the plugin or the config settings file, and confirm that the Cache-Control
response header is returning the expected value.
To help address the issue of pages already cached locally in browsers, I’ve created a module that attempts to clear the local browser cache whenever a visitor first visits any page on the site.
You can install it using composer.
composer require putyourlightson/craft-clear-browser-cache
Once installed, this module sends a Clear-Site-Data response header with the value of "cache"
. This tells browsers to clear the local cache for the entire website (this includes pages, images, style sheets, etc.). The module also sets a cookie named BrowserCacheCleared
, so that the local cache is only ever cleared once per unique visitor.
Note that the Clear-Site-Data
header has limited browser compatibility so is unfortunately not a silver bullet, meaning that some users may continue seeing stale versions of pages until they refresh the browser.
What next? #
I’ll be contacting organisations that have purchased new licenses of Blitz since April directly, to let them know about this issue and offer support. If you require support or have any follow-up questions (or words of wisdom), please get in touch with me at ben@putyourlightson.com.
My sincere apologies for letting this happen (and for not catching it sooner)!