11

I host a static website on S3 + Cloudfront. To redeploy, I upload static files with

aws s3 sync static

and invalidate the cloudfront cache with

aws cloudfront create-invalidation

What is the recommended way to force browsers to get these new assets after I update them? The problem is that browsers are caching these assets and users are getting old (invalid) versions of scripts, images, and styles.

Will
  • 1,171
  • 2
  • 14
  • 26

2 Answers2

13

Generally, there are multiple steps you can do to make sure your AWS CloudFront and S3 setup to cache bust upon new deployments.

  • Make sure you invalidate the cache for index.html (If this is cached)
  • You can either have query parameters or new file names for static assets such as JavaScript, CSS & etc.

Using New File Names

<!doctype html>
<html lang="en">
  <head>
     <link href="styles.h2d1f722.css" rel="stylesheet" />
  </head>
  <body>
     <script type="text/javascript" src="scripts.cbe3c974.js"></script>
  </body>
</html>

You can generate new file names using your frontend build tool (e.g Webpack, Gulp & etc.)

Using Query Parameters

<!doctype html>
<html lang="en">
  <head>
     <link href="styles.css?hash=h2d1f722" rel="stylesheet" />
  </head>
  <body>
     <script type="text/javascript" src="scripts.js?hash=cbe3c974"></script>
  </body>
</html>

When setting up Query Parameters, make sure you have enabled it in CloudFront (Otherwise the cached response of the file will be returned).

Note: Comparing these two approaches there are pros and cons of each of them. Having same file name, you are able to version the files using S3 native versioning while having new names, it doesn't make much sense to do it since new deployments add new names for the files. Also having new file names, makes the S3 bucket cluttered unless you remove or move the old file to another bucket.

  • Make sure you have appropriate values Meta tags in the index.html (Cache-Control, Expires, and Pragma Headers).

Using Meta Tags

<!doctype html>
<html lang="en">
  <head>
     <meta http-equiv="cache-control" content="max-age=0" />
     <meta http-equiv="cache-control" content="no-cache" />
     <meta http-equiv="expires" content="0" />
     <meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
     <meta http-equiv="pragma" content="no-cache" />
     <link href="styles.css?hash=h2d1f722" rel="stylesheet" />
  </head>
  <body>
     <script type="text/javascript" src="scripts.js?hash=cbe3c974"></script>
  </body>
</html>

In this example, it instructs not to cache at browser. However, you can setup appropreate values for these.

  • Practice a versioning scheme for static assets, so that even if an older version of the index.html is served to the end user (Cached from the browser), still the web page loads with old assets (JS, CSS & etc.) without any issues.
Ashan
  • 18,898
  • 4
  • 47
  • 67
  • Hi @Ashan, I'm struggling with this as well. What about the metadata props in S3? Are they still necessary with these headers in place? – Carlos Delgado Mar 26 '18 at 08:12
  • So CloudFront reads the HTML file contents to find out the cache policy to apply to each file? – Carlos Delgado Mar 26 '18 at 12:13
  • 2
    @CarlosDelgado Sorry for making you confused (I have deleted the previous comment). There are two levels of caching. 1) At CloudFront Edge Cache 2) At Browser Level Cache For 1) the TTL configuration in CloudFront applies, For 2) Index.html Meta tags and S3 metadata (Some of them) applies. – Ashan Mar 26 '18 at 16:08
  • 1
    Hi @Ashan, thanks for the clarification. I think it’s worth mentioning that there are more options here, and they all are well explained here: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Expiration.html#expiration-individual-objects – Carlos Delgado Mar 26 '18 at 22:00
2

You cannot force browsers remote if they already hold the cache values, unless manually intervened.

You need to append

script.something.js?buildid=someuniquereference

and make cloudfront not to cache on query string parameters.

You can also include filename.hash.js or filename.hash.html and with index.html / default document, reduce the time of caching with cache control headers.

This way if you make any changes, you can change that number, cache will be busted on the client as well.

But once you sent the cache headers, there is no way you can clean up the client cache on the browsers remotely.

Hope it helps.

Kannaiyan
  • 12,554
  • 3
  • 44
  • 83
  • How can I tell the browser not to cache the assets on S3 + Cloudfront? – Will Mar 23 '18 at 19:54
  • @Will in `script.something.js?buildid=someuniquereference`, someuniquereference will change with every build and whenever you will be building your new assets this will force browser to reload assets from cloudfront and as you have already created the invalidation cloudfront will serve new content. – Shubham Bansal Mar 23 '18 at 20:13
  • Do all browsers respect querystring params? – Will Mar 23 '18 at 20:26
  • Yes. https://stackoverflow.com/questions/9692665/cache-busting-via-params – Kannaiyan Mar 23 '18 at 20:36
  • 1
    Note that the official solution when doing this is to use an `x-` prefix on query string parameters that you want S3 to disregard, even if their API introduces a new fearure. There are certain prefixes that the service will misinterpret, like `tagging` and `select` because it assumes you are trying to access a subresource, rather than the object... so e.g. `x-buildid=...` is probably a more future-proof choice of arbitrary key. – Michael - sqlbot Mar 23 '18 at 23:30
  • @Michael-sqlbot Do you have a link that explains these? – Kannaiyan Mar 24 '18 at 02:01
  • So from my understanding, if I use the query string method I don't need to invalidate the files, right? – Carlos Delgado Mar 26 '18 at 08:18