48 lines
2.4 KiB
Markdown
48 lines
2.4 KiB
Markdown
---
|
|
title: "Browser Caching: Assets not revalidated when server sends a 304 'Not Modified' for html page"
|
|
date: 2022-10-15T20:56:36-04:00
|
|
toc: false
|
|
images:
|
|
tags:
|
|
- dev
|
|
- web
|
|
---
|
|
|
|
I've been working on some web server middleware, and hit a weird issue that I
|
|
couldn't find documented anywhere. First, let's look at an overview of how
|
|
browser caching works:
|
|
|
|
If your web server sends an
|
|
[ETag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag) header in
|
|
a HTTP response, the web browser may choose to cache the response. Next time the
|
|
same object is requested, the browser may add an
|
|
[If-None-Match](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match)
|
|
header to let the server know that the browser might have the object cached. At this point, the server should respond with the
|
|
[304 Not Modified](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/304)
|
|
code and skip sending the response. This can also happen with the
|
|
[Last Modified](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified)
|
|
and
|
|
[If-Modified-Since](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since)
|
|
headers if `ETag` is not supported as well.
|
|
|
|
After implementing this in my middleware, I made a quick test website to try it
|
|
out. That's when I ran into a weird behavior: the browser would revalidate the
|
|
HTML page itself with the `If-None-Match` header, but when the server responded
|
|
with `304` it would not attempt to revalidate the linked stylesheets, scripts,
|
|
and images. The browser would not request them at all and immediately use the
|
|
cached version. It looks like if the server responds with `304` on the HTML
|
|
page, the browser assumes that all the linked assets are not modified as well.
|
|
That means that if the asset does change (you weren't using something like
|
|
fingerprinting or versioning on your assets), then the browser will use outdated
|
|
assets. Oops!
|
|
|
|
Luckily it looks like there's an easy solution: add `Cache-Control: no-cache`
|
|
header to your responses. `no-cache` doesn't actually mean "don't cache at all",
|
|
but rather means that the browser needs to revalidate objects before using
|
|
the cached version.
|
|
|
|
Without the `Cache-Control` header:
|
|
![Browser developer tools window, there is only 1 request for /](/img/browser-caching-before.png)
|
|
With the `Cache-Control` header:
|
|
![Browser developer tools window, there are 5 requests in total, including /, style.css, and 3 images.](/img/browser-caching-after.png)
|