You reload a page and see 304 Not Modified beside a request for its stylesheet. The page still has its colors and layout, even though the response carried no stylesheet. The missing bytes came from a copy the browser already had. The browser asked whether it could reuse that copy, and the conditional response gave it the answer it needed.
That small exchange is easy to confuse with a different one, where the browser uses a fresh copy without contacting a server at all. The distinction matters when you are trying to work out why an edit has not appeared, why a reload still sends traffic or why a fast response in your browser does not match what someone else sees. The request headers and the cache policy can explain more than the status column alone.
Freshness and validation answer different questions
A cache keeps responses so it can reuse them when another request needs the same representation. In HTTP, a representation is a particular form of a resource. The same URL can serve different forms, such as a compressed or uncompressed stylesheet or a document in different languages. Reusing a response requires a match for the request and permission under the applicable cache rules.
Freshness answers whether a stored response can be used without asking for validation. A server can use a response directive such as Cache-Control: max-age=120 to provide a freshness lifetime of 120 seconds. The cache compares the lifetime with its calculation of the response’s current age. A fresh response can satisfy an ordinary matching request directly when the other reuse conditions allow it. You might see a browser indicate that it loaded the resource from memory or disk, without a new HTTP response being transferred to the browser for that reuse.
Validation answers whether the selected stored response can be used after a check. A cache can send a conditional request with information about the response it holds. If the validator still matches, a 304 can let it keep using the stored content. If the validator differs and the server otherwise succeeds, the response can be a 200 with new content. Some requests force validation even when a response would otherwise be fresh, so a 304 by itself tells you little about the age of the stored copy. Read the policy and request together. RFC 9111 explains freshness and validation separately.
Follow one stylesheet through three visits
Imagine an example site that serves /layout.css. The browser has never stored it. On the first visit, the response is 200 OK and includes the stylesheet body, a freshness lifetime of two minutes and the entity tag "layout-1". An entity tag, sent in the ETag response header, is a value the sender associates with the selected representation. Its contents are opaque to the recipient. In this example, the tag is readable for teaching purposes, but a browser has no reason to interpret the number as a release number.
Suppose the same browser visits another page on the site a minute later. It needs the same stylesheet under the same request conditions. If the response is fresh, reusable and still stored, it can load the CSS directly. This visit is a useful control for the example because no 304 is needed. It also illustrates why checking only the server’s request log can miss some resource use entirely. A browser that already has everything it needs may never ask the server on that visit.
After the freshness lifetime has passed, the browser wants to check its stored copy. It sends a GET for /layout.css with If-None-Match: "layout-1". The tag on the selected representation is still "layout-1". The server replies 304, with applicable response headers and no stylesheet body. The browser combines the validation information with its stored response and uses the bytes it has. For an actual page view, the stylesheet still applies.
An editor later changes the layout, and the server now selects a representation with ETag "layout-2". At the next validation, a GET using If-None-Match: "layout-1" no longer matches that tag. If the request succeeds normally, the server sends 200 with the updated CSS and the new tag. The browser can store the new response if the policy permits it. The illustrative network sequence is 200, 304 and 200, with a separate opportunity for a fresh cache reuse between the first two exchanges. None of the timing in this example is a measurement of a live site.
Read the validator in the request
Look for If-None-Match in the request and ETag in the prior response. If you are debugging an unfamiliar browser capture, start with one resource and note its URL, request method and relevant headers. On a conditional GET or HEAD, a matching tag causes the If-None-Match condition to fail, leading to a 304 in the ordinary successful retrieval case. The header name makes more sense if you read it as a request to send the representation only if the tag does not match.
Some tags start with W/ before the quoted value. That prefix marks a weak validator. It can still be useful for checking whether a stored response is suitable for ordinary reuse, even when the representation data are not byte-for-byte identical. If-None-Match uses weak comparison for tags. This is one reason to avoid telling a reader that a 304 always proves that two files have identical bytes. Strong validators are suitable for uses that need a finer distinction. Keep the prefix and quotation marks when copying a tag into a diagnostic note so the value retains its meaning. RFC 9110 describes entity tags and the If-None-Match condition.
You may also see Last-Modified on a response and If-Modified-Since on a later request. Those fields use a date to express a modification-time condition. When both If-None-Match and If-Modified-Since are present, HTTP gives precedence to If-None-Match. Do not assume the timestamp decided the outcome simply because the two dates look the same in a capture. If no tag condition is present, the date condition can be the relevant clue. RFC 9110 covers the date condition.
Check which version of the resource was selected
Before comparing tags or timestamps across two devices, check what each device requested. If a site negotiates its response according to a request header such as Accept-Language, it can use Vary to tell caches which request field matters when choosing a stored response. A French document and an English document can share a URL while being separate representations for caching purposes. The same principle can apply when a server varies content according to Accept-Encoding. Copy the Vary value and relevant request headers into your notes before concluding that one device has the wrong version.
Imagine two people opening the same public help page. The first requests French and receives a French response with Vary: Accept-Language. The second requests English. You would want the cache to select a stored response suitable for the second request rather than reuse the French body just because both requests used the same path. If you only compare the URLs and the status codes, you skip the information that explains why their screens differ. RFC 9111 explains response selection using Vary.
Cookies, authentication and shared caches make this comparison more consequential. Start with a public, non-personalized resource when learning the technique. A test with two different logged-in users also involves questions about whether the response may be stored or reused by an intermediary. Treat personalization and cache isolation as separate checks and consult the application’s requirements before drawing a conclusion from a validator alone.
Know what the 304 response can carry
A 304 has no content body or trailers, but it can carry useful headers. For example, a server includes certain fields if they would appear on the corresponding 200 response, such as ETag, Vary or Cache-Control. This lets a recipient update relevant metadata for the stored response. A renewed freshness policy can therefore appear on a response with no new representation bytes. That is quite different from assuming a small response can never affect subsequent requests. RFC 9110 defines 304 and RFC 9111 covers updating stored responses.
A 304 still involves an HTTP exchange somewhere along the path. It can reduce transferred bytes, especially when the representation is large, but it cannot by itself establish that there was no network delay or that the request reached the origin. An intermediary may participate in validation. If you have only a browser capture, say what the browser observed. To tell whether an origin handled a particular request, look for a separate origin-side record or an appropriate trace. This distinction matters when investigating infrastructure load rather than simply how long a page took to render.
Likewise, a 200 entry does not always mean that every hop fetched the file from scratch. A shared cache can supply a reusable stored response to a client that made an ordinary request. A browser-facing status, a transfer-size figure and a server log are observations from different places. Use each to answer a question it can support.
Check these details when an edit looks stale
First, identify the resource that contains the change. It could be a stylesheet, a script, an image or a data response rather than the page document. A 304 on an unrelated icon tells you nothing about whether the corrected stylesheet reached the browser. Check whether the page points to the expected URL and whether the returned resource contains the expected edit. For a build that uses filenames derived from content, a newly edited asset can be published under a different name while an older page still points to the old name. Look at both responses in that case.
Next, record the normal request before changing browser controls. A cache-bypassing reload can be useful as a comparison, but it changes the conditions you are trying to understand. A good comparison keeps the URL, representation and browser session constant where possible, then changes one factor. For example, inspect the ordinary load and then a reload with the browser’s cache control changed. Write down which you used. Even if that reload shows the edit, the experiment alone cannot explain the policy of every shared cache between the browser and the origin.
Then read Cache-Control carefully. A response with the ordinary no-cache directive can be stored under the applicable rules, but a cache must validate it before reuse. No-store tells caches to refrain from storing the response under the rules for that directive. The names sound similar when you say them quickly, and confusing them can send you looking for a caching bug where the response is behaving as configured. RFC 9111 explains response cache directives.
Finally, record what happened in plain language. A useful note might say that the stylesheet request carried the old tag, returned 304 and reused a stored copy, while the page document still linked to the previous stylesheet URL. The next check in that example would be the document’s asset reference and cache behavior. If a 200 with a new tag arrived and the layout still looked old, inspect the returned CSS and its position in the cascade. The visual symptom can outlast a cache issue or have another cause entirely.
Version labels can be useful in other kinds of technical investigation, too. Our guide to using a software bill of materials during a vulnerability review starts with the version and scope of an inventory. In a browser capture, the corresponding habit is to write down which URL and request headers produced the response before comparing it with another capture.
Read the status as part of the exchange
When you see 304 Not Modified, identify the resource, the conditional header and the version selected for that request. Then check the cache policy to understand whether a fresh copy could have been used without validation and whether metadata changed during the check. These details can distinguish a browser doing exactly what it was asked to do from a site delivering the wrong resource. They also give you something concrete to hand to the person who owns the server, the intermediary or the page.
Sources consulted 5 November 2026. The stylesheet trace and language examples are illustrative.
