What can we build today?

Random notes of a Webdeveloper called Mario.

Updated 2015-04-01, added solutions E, F and G
Updated 2017-02-22, updated solution D

The problem

Given you work on a project and continuously deploy new versions of the site, you'll probably also deliver fresh static assets like CSS and JS files. We noticed that our changes (including bugfixes) were not recognized by the browser of the user/customer... The reason was mostly, that the CSS/JS files were cached locally by the browser from an earlier visit.

So we needed to find and implement a solution to fix that issue by somehow breaking the local browser-caching for those files. The easiest to achieve that is by modifying the URL of those assets e.g. from foo/bar.js to foo/bar.js?v2

Due to the changed URL, the local browser will treat the second version as a completely new asset.

As we've found out, there are way more ways to achieve this with Neos than we expected at first. Below we show the several ways we found or were referred to:

Solution A: Add the timestamp at rendering time

The probably simplest solution is to extend the URL directly in the Fluid template where you include your static files for the site template and/or application:

While this is simple, it's also limited to single files where you change the linking as shown above - but not for other files, especially images that are uploaded by editors.

In our tests, it also showed that this solution is not always the best, as the timestamp can change upon each request - so the browser-caching for those assets is rendered useless completely. If the whole page can be cached, fine - otherwise the timestamp is changed upon each time the page is being rendered.

Solution B: Return proper ETags on the webserver

Basically the webservers and browsers should be able to detect that by the use of ETags - which is a meta-data that changes whenever the content of a file changes. In certain situations, this won't work out as you might not have enough control over the webserver to enable/configure/modify the ETag feature.

Solution C: Add the timestamp of the file itself via an Aspect

Several persons have brought up the solution to "extend" the Fluid Viewhelper that generates the URI to the static asset itself. This is achieved by using an Aspect to be executed right after the viewhelper has generated the URI (and then to further modify that string).

The code for that can be found as a Gist

It's IMO a great use-case that shows, how an existing functionality can be extended without actually modifying the original code or overriding it completeli (as with Xclassing in TYPO3 CMS back in the days). Also it's great that it takes the last modification time of the file itself and thus changes the cache-breaker timestamp only when the file get's actually modified.

Solution D: Add the timestamp of the file directly in the ViewHelper

Only thing that speaks against this approach is, that it would be easier to have that functionality directly implemented in the view-helper itself, isn't it?

There was a feature-request for it being discussed, but then decided to be not implemented in the core. The reason was that delivering static assets with proper eTags seems to be the preferred way - and that this functionality should rather be built in a separate package, for the rare cases where eTags won't fit for some reason - or are just not available.

This package has now been built by Dmitri Pisarev and me: https://packagist.org/packages/opsdev/cache-breaker

Configuring your application to use the CacheBreaker-Package will lead to resources being fetched like /_Resources/Static/Packages/Vendor.Package/css/styles.css?91888cf3 - which is not actually the mentioned timestamp, but a shortened md5-hash of the content of that file. So in case the file changes, the hash will also change and thus a new URL get's generated.

Feedback and improvements to the package are very welcome!

Solution E: Change "timestamp" during build process

While solution D might be sexier compared to solution C, both are still not targeting the right place, if looking at the issue from a birds-eye perspecitve (on the architectural level):

Adding a timestamp via the viewhelper adds work-load to the production system each time the page is being rendered, which is bad. Since we expect those static assets to stay the same as long as the developer doesn't change them and re-deploys the website, it doesn't make sense to re-evaluate the file's last changed date and add the same again and again. So it should happen right then when the assets are generated (e.g. minified CSS and JS files):

You can either do that yourself by manually updating the cache-breaker GET-Parameter manually in your Fluid-Template each time you modify/commit/push/release a new version:

<link rel="stylesheet" href="{f:uri.resource(path: 'foo/bar.js', package: 'Vendor.Package')}?v999" />

Of course that's tedious and error-prone and not a real option. But with some kind of automation, this can be optimized. If you're eg. using Gulp to automate building your merged/minified assets, you could use one of the following plugins:

  • gulp-rev-mtime, adding the modification timestamp of the file
  • gulp-version-append, very simplistic, just adding a globally configured version-string (thus only useful for releases with version numbers)
  • gulp-rev-append, my favorite, adds an md5-hash as GET-Parameter which changes as soon as the content changes

All those Gulp-Plugins are not tested yet, let me know if they work :-)

This seems to be a very structured way to add the cache-busting. The only downside I see is that it adds "unneded" changes: A single line change on a *.scss file will anyway also affect the compiled *.css file - but now it would also cause a change in all the template files that link to that *.css-File.

Solution F: Generate assets on the CI-Infrastructure

This would be the same as Solution E - but executed during a build-and-deploy job on the Continuous Integration system of your choice.

The result would be:

  • Developers only commit changes to the source files (e.g. *.scss)
  • The locally "built" *.css files would not be versioned
  • Also the local build task would not update the template (e.g. no cache-busting on the dev's instance)
  • The CI-System would then execute a build task for the static assets, generating the *.css files and modifying the template's paths to the static assets (here the cache-busting would be added)
  • Those generated/modified files would then be deployed to the target server

From an architectural perspective, this sounds like a very clean solution.

One point that could speak against it is, that there might be a "policy" (or just a feeling), that only what is tracked via the code-versioning should be deployed to LIVE. On the other hand: having the whole build config versioned with the Code, both the local developer's system and the CI-Sysetm should result in the same output, given the same input.

Solution G: Fancy solution

Steffen Müller pointed me to a tutorial involving the Twig templating language which goes way beyond simple cache-busting - it delivers some CSS inline if the user visits the page for the first time. And later on adds some magic with JavaScript and depending on the presence of a cookie.

Regarding Neos, I'm not sure if a server-side switch between first-time and returning visitors really improves the perceived performance of a website as this will require to render the page upon request, instead of returning a cached version of the page. Without testing this approach, I guess that little time-improvement gained with inlining the CSS might be eaten up by the added time of regenerating the page (and inlining the CSS) on the server side.

I'm adding it here nonentheless, because it shows a little detail: Cache-Breaking doesn't need to happen with a GET-Parameter like foo.css?v99 but can also happen like foo.v99.css if the web-server can handle and rewrite this request to foo.css.