Laravel optimization : static content caching and cache busting

Jul 11, 2021 by Thibault Debatty | 705 views

Laravel Cyber-Wise

https://cylab.be/blog/157/laravel-optimization-static-content-caching-and-cache-busting

Optimizing you web app from the browser side is an important concern, to provide a pleasant experience to your users. It will also reduce the traffic on your servers. In this post we show how to implement static content caching and cache busting on a Laravel application.

Initial assessement

webpagetest.org is a great tool for analyzing the browser side performance of a web application. The example below is taken from an early version of our Cyber-Wise project. This project is a pretty standard Laravel app, with a VueJS frontend.

As you can see, the site gets an "F" rating regarding static content caching. This is simply because, by default, no content is cached on the browser. This means that for each page view (each click), the browser must download all content, including images, JS or CSS files that may already have been downloaded previously.

As a result, the users will perceive the site as slow. This will also result in a lot of uploaded traffic for your servers.

Static content caching

To indicate to the browser that static content should be kept in cache, you can add the following configuration to your .htaccess:

# https://cylab.be/blog/157/laravel-optimization-static-content-caching-and-cache-busting
<IfModule mod_expires.c>
  ExpiresActive on
  ExpiresDefault                          "access plus 0 seconds"

  # Favicon
  ExpiresByType image/x-icon              "access plus 1 month"

  # CSS and JavaScript
  ExpiresByType application/javascript    "access plus 1 month"
  ExpiresByType text/css                  "access plus 1 month"

  # Media: images, video, audio
  ExpiresByType audio/ogg                 "access plus 1 month"
  ExpiresByType image/gif                 "access plus 1 month"
  ExpiresByType image/jpeg                "access plus 1 month"
  ExpiresByType image/png                 "access plus 1 month"
  ExpiresByType video/mp4                 "access plus 1 month"
  ExpiresByType video/ogg                 "access plus 1 month"
  ExpiresByType video/webm                "access plus 1 month"

  # Webfonts
  ExpiresByType application/vnd.ms-fontobject "access plus 1 month"
  ExpiresByType application/x-font-ttf    "access plus 1 month"
  ExpiresByType application/x-font-woff   "access plus 1 month"
  ExpiresByType font/opentype             "access plus 1 month"
  ExpiresByType image/svg+xml             "access plus 1 month"
</IfModule>

This way all static content (images, videos, CSS files, JS files and web fonts) will be downloaded only once, and kept for 1 month. However, there is a drawback with this agressive caching: if you update your app, some users may continue using the old cached files for up to 1 month.

For media files, there is on manual solution: rename the file. When you update an image or a video, you should always use a new filename to make sure the new version is used by the browser.

For the CSS and JS files of your Laravel app, there is an automatic solution called cache busting.

Cache busting

With cache busting, a hash is added to the name of your CSS and JS files when you compile them. Hence each time you update and compile your code, a new filename is automatically generated.

To enable cache busting, you must add a call to the version() method in webpack.mix.js:

mix.js('resources/js/app.js', 'public/js')
    .version();

Now you can rebuild your JS and CSS files with

npm run production

And you should see the appended hash in public/mix-manifest.json:

{
    "/js/app.js": "/js/app.js?id=47af4b51e1e17baabe3c",
    "/css/app.css": "/css/app.css?id=a80c053fa4397762a633"
}

In your views, you should use the full filename (with the hash). For this, you must use the mix function (instead of asset) in your views:

<script src="{{ mix('/js/app.js') }}"></script>
<link href="{{ mix('css/app.css') }}" rel="stylesheet">

Final result

With static content caching in place, you will get a grade A or B.

This will result in a better experience for your users: the site will seem to respond faster because static content will not be downloaded at each page view.

This will also impact your servers, and on your wallet: without content caching, each page view (repeat view) requires to download roughly 1,5MB of data from the server. If you have an entry-level hosted server, your are probably limited to 100 Mbit/s of traffic. Hence your server will be able to serve roughly 6 pages per second because of bandwidth limitation.

With static content caching, visiting a page (repeat view) requires to download only 80KB. This time the same sever can serve more than 100 pages per second. Likewise, at the end of the month the total amount of data uploaded by your server will be greatly reduced, which will also reduce your bill if your pay by GB.