304 (Not Modified) responses minimize the amount of information that needs to be transferred in subsequent requests made after an initial 200 (OK) response if the resources being requested are unchanged1.

This has performance benefits for a website, particularly where larger assets such as images are involved.

If you are developing a website that makes use of custom headers in responses, it’s worth noting a behaviour with respect to headers that 304 responses have that differ from 200 responses.

If a visitor to the website were to inspect their network requests and look at a 200 response, they can expect to see not only custom headers defined for the response, but the following headers which relate to the caching of the resource2:

  • Cache-Control
  • Content-Location
  • Date
  • ETag
  • Expires
  • Vary

In a 304 response however, the specification details that1:

… a sender SHOULD NOT generate representation metadata…unless said metadata exists for the purpose of guiding cache updates.

This means that that same request, if the resources are cached on the browser, will receive a response that will generally be missing the additional metadata (in this case the custom headers) since they don’t tend to serve ‘the purpose of guiding cache updates’.

As an example to illustrate this behaviour, let’s use a demo site that Netlify has for the Next.js framework. The specific page that we’re interested in can be found here.

Looking at the ‘Network’ tab within the browser console, the request fetching the image on the page is the one that we’ll take a closer look at. It should look something like:

GET https://netlify-plugin-nextjs-demo.netlify.app/_ipx/w_3840,q_75/%2F_next%2Fstatic%2Fmedia%2Funsplash.9a14a3b9.jpg?url=%2F_next%2Fstatic%2Fmedia%2Funsplash.9a14a3b9.jpg&w=3840&q=75

and the 200 response headers:

HTTP/2 200 OK
age: 0
cache-control: public, max-age=0, must-revalidate
content-type: image/jpeg
date: Tue, 23 Aug 2022 01:05:36 GMT
etag: "3b-XMihzCU33fI+mTMhk6V4yc9TZww"
server: Netlify
strict-transport-security: max-age=31536000
x-nf-request-id: 01GB43QGZ949EAFF6HBMX480RG
x-test: foobar
X-Firefox-Spdy: h2

Now if we were to refresh the page, we’ll see a 304 response for that request instead:

HTTP/2 304 Not Modified
cache-control: public, max-age=0, must-revalidate
date: Tue, 23 Aug 2022 01:08:03 GMT
etag: "3b-XMihzCU33fI+mTMhk6V4yc9TZww"
server: Netlify
x-nf-request-id: 01GB43W0VGYVR87CHSMZ70T4HQ
X-Firefox-Spdy: h2

Notice how the x-test custom header near the bottom of the list for the 200 response is now missing from the 304 response.

Wrap up

I hadn’t realized until recently that the minimization of data in a 304 response extended to the response headers, and so for those who have run into this behaviour and were also a bit puzzled as to why this happens, I hope this quick explanation was helpful.

Happy coding!

  1. https://httpwg.org/specs/rfc7232.html#status.304 ↩︎

  2. https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/304 ↩︎