Enhancing Application Performance with Client-Side Caching: Cache-Control and ETags.
Introduction
Currently, I’m working on a project where I’m using cache control headers and ETags to take full advantage of client-side caching. It’s been such an interesting experience that I’m sharing the concept in this article
Need of caching
We all understand what caching is and why it's so important, but let me quickly give you an overview because I’m diving deep into the caching approach and why it’s a game-changer in performance.
Every time we visit a website, our browser requests data from the server. Without caching, it fetches everything fresh every single time, even if nothing has changed. This slows down our load times. Caching solves this by storing frequently used data (like images, scripts, styles, or JSON data) in cache memory. So we can reuse it instantly without repeating the heavy tasks.
Where do we cache our data?
We can cache our data in different places:
Client-Side Caching
Suppose we have data that doesn’t change often but is accessed frequently. For this type of data, we can cache it in the browser. By doing so, the user's requests won’t go to the server every time. Instead, the data will be served directly from the browser cache, improving load times and reducing server load.
Client-side caching is crucial for improving website speed and performance. It allows data to be stored closer to the user, reducing the need for repetitive requests to the server. This results in faster load times, reduced server load, and a better overall user experience.
This is where the Cache-Control header becomes essential. It’s a simple and powerful tool that lets us control how and for how long data should be cached.
Cache-Control Header
The Cache-Control is an HTTP header that can be included in both requests and responses. It serves as a set of instructions for the browser, telling it how to handle caching for a specific resource.
When the browser receives a resource with this header, it uses the values specified in it to determine whether and how to cache the resource. This caching can occur at various levels, such as the browser, CDN, etc.
Here's how to set it up in (node.js / Nest.js):
res.set('Cache-Control', 'public, max-age=3600'); // Cache for 1 hour
All we need to do is follow the steps above—it's that simple! Easy, right? ??
Key Directives in Cache-Control
The Cache-Control header is a highly effective way to cache static data, such as HTML, CSS, image files, and even formatted data when needed.
Cache-Control Sets the Rules, ETag Checks the Freshness!
While the Cache-Control header is great for specifying how long resources should be cached, sometimes we need a way to check if the cached resource is still valid. For example, what if the resource changes on the server before the cache expires? This is where ETag and the 304 Not Modified status come into play.
I mean, Cache-Control sets the rules, But ETag brings the gossip: Is Your Cache Still Fresh? ??
ETag (Entity Tag)
An ETag (Entity Tag) is like a unique fingerprint for a file or resource on a server. It helps the browser check if the file has changed since it was last cached.
Here’s how it works:
1. The server Assigns an ETag:
When a client requests a resource, the server generates a hash value using the resource. Then use it as a ETag header in its response (I mean header name is Etag and the value is generated hash value). For example:
HTTP/1.1 200 OK
Content-Type: application/json
ETag: "abc123"
2. The Client Stores the ETag:
The client stores the ETag value along with the resource in its cache.
领英推荐
3. The Client Sends a Conditional Request:
When the client requests the same resource again, it includes the If-None-Match header with the previously stored ETag:
GET /resource HTTP/1.1
If-None-Match: "abc123"
4. The server validates the ETag :
a. The server retrieves and processes the data, and then generates a new hash based on the fetched content.
b. If the ETag doesn't match (indicating the resource has been modified), the server sends the updated resource along with a new ETag.
c. If the newly generated hash matches the provided If-None-Match header value, it means the content hasn't changed yet. In this case, the server responds with a 304 Not Modified status and omits the resource body, signaling the client to use its cached version.
HTTP/1.1 304 Not Modified
How the hash can be generated?
The server generates a hash using the data of the resource it serves. Typically, this is done by converting the resource into a consistent format (e.g., JSON) and then applying a hashing algorithm like MD5, SHA-1, or SHA-256.
Let’s say it is our data:
[
{ "id": 1, "name": "A" },
{ "id": 2, "name": "B" }
]
To generate the hash for this data, First, we need to serialize it (format it into a string)
// After converting into a string
'[{"id":1,"name":"A"},{"id":2,"name":"B"}]'
Then hash the string using a hashing (Let’s say SHA-256) algorithm:
Hash: a84d97d60d294472b8d3f6b74bc3327c1ebcd05f
We can return the hash as the ETag.
Advantages of This Approach
Limitations
Best Practices to Overcome Limitations and Achieve Optimal Results
Combining ETags with other HTTP headers like Cache-Control and Expires can help improve caching and get the optimal benefits considering the limitations:
Example Response Headers
HTTP/1.1 200 OK
Content-Type: application/json
ETag: "1234567890abcdef"
Cache-Control: public, max-age=3600, must-revalidate
Expires: Mon, 20 Jan 2025 10:00:00 GMT
How It Works
In short, Feed the data from the cache for an hour, then send it on a 'Is this still fresh?' mission to the server because even the cache needs a reality check!
Conclusion
To sum up, I explored the power of Cache-Control headers and ETags to optimize client-side caching for my project. I aim to share what I’ve learned, create a resource for others, and welcome any feedback on topics I might have missed. Thank you for reading, I truly appreciate your time! ??