How to Cache web app?
Tags: react, s3, cloudfront, devops
Published: 2022-06-05 12:54:36


I would like to share my learning on how we were able **to cache web app** and then **bust the cache** on every deployment so that the users can experience the latest changes.

---

### Why do we need to cache ?
Well obviously why would you do the same work again, if you can save and reuse it.

In other words, lets say you need something and have to travel a long distance, so you keep it somewhere near to access it quickly.

---

Before I go into more details, would like to brief you on my tech stack:

- **React app** using [create-react-app](https://create-react-app.dev/)
- Using aws [**s3**](https://docs.aws.amazon.com/AmazonS3/latest/userguide/Welcome.html) to upload and host the static build
- [**Cloudfront**](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Introduction.html) to cache it on the edge locations, with `s3` origins

---

## How did we cached before and why it was not a good approach

We have created a script that was used in our pipeline to deploy our app to `s3`:

{% embed https://gist.github.com/m-nathani/178d4e38549d8d42d3a9dc42fe30c475.js %}

### What's happening in above script ?
We are using aws cli to upload build to `s3` using [s3 sync](https://docs.aws.amazon.com/cli/latest/reference/s3/sync.html#examples) command which upsert and delete files.

Important thing to notice is that we are handling the cache on `s3` with `static` folder being cached and rest is not.


Well coming back to tech stack we are using `s3` to upload builds and `cloudfront` to cache, then why the hell are we using `s3` to cache.. :D

---

## How did we manage to change the approach

Using one of the rule of **SOLID** is [single usability principle](https://en.wikipedia.org/wiki/Single-responsibility_principle)

[Robert C. Martin](http://blog.cleancoder.com/) `describes it as:`

>  **A class should have one, and only one, reason to change.**

Using `s3` to just upload the files, of what is purpose is like:

```bash
#!/bin/bash

if [[ "$1" != "" ]]; then
    S3BUCKETNAME="$1"
else
    echo ERROR: Failed to supply S3 bucket name
    exit 1
fi

aws s3 sync build/ s3://$S3BUCKETNAME --delete

```

and using `cloudfront` to create [Cache behavior](https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_CacheBehavior.html) using unique `path` patterns in the order of priority to apply.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eqn4gp5xwdmfoyz7g22q.png)
 
As shown in the above image we created three behaviors, with its specific purposes to cache or not cache. Make note that `index.html` is **not cached** because its entry point of our app and we want this file to be always updated on new deployment, hence it will always refetch from `s3` origin. 

Moreover, you can use existing cache policies provided by `AWS` or create your own [here](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-cache-policies.html?icmpid=docs_cf_help_panel) 


P.S: if you want to use a cache header such as `no-cache` in `CloudFront` to perticular resource in `origin`, then we can add headers to `s3 origin`, as I couldn't find a equivalent way to do the following in `CloudFront`:
```bash
aws s3 cp build/index.html s3://$S3BUCKETNAME/index.html --cache-control no-cache
```
Additionally, Note: `no-cache` doesn't mean "don't cache", it means it must check (or "revalidate" as it calls it) with the server before using the cached resource.

#### Further reading
[Caching-best-practices](https://jakearchibald.com/2016/caching-best-practices)

And basically that's it folks!, this is all we needed to manage cache using dashboard of `cloudfront`.

---

## Cache Busting

Well one of advantages of using `build` produced from `CRA` is that it generates a unique hash with file names which automatically gets cache busted on `cloudfront` when we upload to `s3`.

Well another approach to cache busting is invalidating the cache, which is not a good approach as it's relatively slow, and could get expensive fast seeing as `cloudfront` gives you just `1,000` free invalidation's per month, and then charges `$0.005` per invalidation path requested as at the date of writing.

Besides, it’s pretty clear that [CloudFront recommends Object Versioning or unique file names over invalidation](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html#Invalidation_Expiration)…

>If you’ll want to update your files frequently, we recommend that you primarily use file versioning

---

## Conclusion

- Here we learned how to manage cache using `cloudfront`, rather than writing your own `bash` scripts and managing it

- Making full use of `cloudfront` to update and reuse the cache policies between different `origins` or `distributions`

- Can add your own regex as `path` in cache behavior

- No need of invalidating cache, as its expensive and not a best approach

---

Would love to hear your thoughts with this approach.
Regards,