Cache busting using Laravel Mix and CloudFront+S3

When you build assets (js, css, images) in Laravel applications, Laravel Mix is very useful. Laravel Mix is a wrapper for Webpack and you can build Vue.js components, Sass and images out of the box.

For basic usage, you don’t need to modify the default configuration file, webpack.mix.js. But when you deploy an app to production, it’s important to take into account cache busting.

In this post, I’ll show you how to implement cache busting using Laravel Mix and Amazon CloudFront+S3.

Cache busting using Laravel Mix

The default config for Laravel Mix is something like this. You can build JS (including Vue.js components) and CSS out of the box!

However, it’s not enough for production app. Browsers cache assets for a long period of time unless URL changes, even if you deploy new assets. So it’s very important to force browsers to load the fresh assets instead of serving stale copies of the code. It’s called cache busting. If you are not familiar with cache busting, read this article.

With Laravel Mix, you can implement cache busting by calling mix.version() like below.

Call mix.version() only for production build

It appends version hash as a query parameter like ?id=28bb485e844345258. Version hash won’t change unless file contents change. That means that assets URL will change when file contents have any updates. As a result, browsers will load fresh contents instead of the cached stale assets.

Serve assets via CDN

The example in the previous section assumes assets are served directly from web servers. That’s o.k for applications that don’t have a large number of traffic. However, if your application has high traffic or performance is critical, you should serve assets from CDN.

In my case, I use Amazon CloudFront and S3 for CDN. By default, Laravel Mix doesn’t cover uploading assets to S3, so I need additional setup.

First, install webpack-s3-plugin.

$ yarn add webpack-s3-plugin

Then, create S3 configuration and pass it to mix.webpackConfig().

I get AWS credentials and bucket name from .env file.

AWS_KEY=YourKey
AWS_SECRET=YourSecret
ASSETS_S3_BUCKET=YourBucketName

In package.json, I have two scripts for production build. The first one builds assets for production but doesn’t upload to S3. The other one uploads to S3 because it sets UPLOAD_S3=true.

Now you can build assets and upload them to S3, but how do you reference CDN assets in blade template? Even though Laravel comes with mix() helper function, it references assets in local server, not the outside location.

Therefore, I implemented a custom helper function that wraps mix() and returns CDN asset URL. I defined CDN base URL in config/assets.php file.

It references CDN assets only in production and staging environment since I wanted to reference local assets during development.
You can use it in blade templates like this 🙂

<script src="{{ asset_path('js/app.js') }}"></script>

Setup CloudFront+S3

Now you can upload assets to S3 and reference asset URL in CDN. If you haven’t setup CloudFront and S3, the basic setup is described in the AWS official docs.

Cache based on query string parameters

By default, CloudFront doesn’t cache contents based on query string parameters. Let’s say, if you have app.css?id=1111 and app.css?id=2222, CloudFront ignores id parameter and just creates cache as app.css.

That means that when you upload a new version of app.css to S3, CloudFront won’t update the cache and will serve stale contents to the client unless you invalidate the cache.

However, you can configure CloudFront to cache contents based on query string parameters. CloudFront will create cache for app.css?id=1111 and app.css?id=2222. A new cache will be created every time you deploy new version of your app.

So, let’s edit CloudFront settings!

Click distribution ID you want to edit
Move to “Behaviors” tab, select a behavior and click “Edit” button

In the “Query String Forwarding and Caching” section, select “Forward all, cache based on whitelist”, add id to "Query String Whitelist" then update the behavior. Laravel Mix adds id query string to each asset url as a version hash.

https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/distribution-web-values-specify.html#DownloadDistValuesQueryString

What about deployment?

It depends on how your deployment workflow is. In my case, I build assets and upload to S3 by executing yarn run production-upload-s3 on CI.

If you have another way of deploying assets, please post a comment 🙂