Home / Blog / Browser Caching Without ?v= Tags: Revalidation Explained
Page Speed

Browser Caching Without ?v= Tags: Revalidation Explained

23 September 2026 · AI Web Page SEO
Revalidation is a browser caching approach where the browser keeps a file but checks with the server before using it. With Cache-Control: no-cache and ETags, the server replies 304 Not Modified if nothing changed or 200 with the new file, so JavaScript and CSS updates reach visitors on their next page view without ?v= tags.

If you've ever updated a JavaScript or CSS file and visitors kept seeing the old version, you've met browser caching. A common fix is adding a version tag, script.js?v=123, and changing it every release. It works, but it's easy to forget, and a forgotten tag leaves visitors on old code for weeks. There's a simpler approach: browser caching with revalidation.

How browser caching works

When a browser downloads a file, the server can say how long to keep it. A header like Cache-Control: max-age=2592000 means "keep this for 30 days without asking again". That's excellent for speed: repeat visitors load pages instantly. It's a problem for files that change, because the browser won't even check for a new version until the time runs out.

Why ?v= version tags go stale

Version tags work by changing the file's address. combined-audit.js?v=1 and combined-audit.js?v=2 are different URLs to the browser, so a new tag forces a fresh download. The catch is that every change needs a new tag, on every page that loads the file. Miss one, and returning visitors keep running old code for the full cache period. On a busy site with frequent fixes, that's a steady source of "I fixed that, why is it still broken?"

What revalidation does instead

Revalidation tells the browser: keep the file, but check with the server before using it. The header is Cache-Control: no-cache, which despite its name does allow caching. The browser sends a small request saying "I have the version with this ETag", and the server answers either:

The cost is one quick round trip per file per page load. For most sites that's barely noticeable, and it means updates reach visitors on their next page view, with no tags to maintain.

Setting up revalidation in nginx

A common setup gives images and fonts a long cache, and JavaScript and CSS revalidation. In nginx, the first matching regex location wins, so the JS/CSS rule must come before the general static-file rule:

location ~* \.(css|js)$ {
    expires epoch;
}
location ~* \.(png|jpg|webp|svg|woff2)$ {
    expires 30d;
}

expires epoch sends Cache-Control: no-cache, and nginx sends ETags for static files automatically.

Watch out for add_header

You might be tempted to write add_header Cache-Control "no-cache"; instead. Be careful: in nginx, any add_header inside a location block stops that block inheriting all the add_header lines from the server level. Your Content-Security-Policy, HSTS and other security headers silently disappear for those files. The expires directive avoids that problem because it isn't an add_header.

Checking it worked

Request a script's headers with curl -sI https://example.com/app.js. You should see cache-control: no-cache, an etag, and your security headers. Images should still show max-age=2592000. Once that's confirmed, the ?v= tags can be removed from your pages.

One thing to remember: visitors who downloaded a file under the old 30-day rule keep that copy until it expires. The change applies to copies fetched from then on.

Summary

Long caches suit images and fonts; revalidation suits JavaScript and CSS that change. With no-cache and ETags, browsers keep files but check for updates on each load, so fixes reach visitors immediately and version tags become unnecessary. To confirm your security headers survive the change, run your page through the security audit.

Frequently asked questions

Why do ?v= version tags cause problems?

Version tags work by changing a file's address, so combined-audit.js?v=1 and combined-audit.js?v=2 are treated as different URLs and a new tag forces a fresh download. The catch is that every change needs a new tag on every page that loads the file. If one is missed, returning visitors keep running old code for the full cache period, which can mean weeks with a 30-day cache. On a busy site with frequent fixes, that becomes a steady source of fixed problems that still appear broken to visitors, which is why revalidation is a simpler alternative.

How do you set up revalidation for JavaScript and CSS in nginx?

A common setup gives images and fonts a long cache while JavaScript and CSS use revalidation. In nginx the first matching regex location wins, so the CSS and JS location block must come before the general static-file block. The CSS and JS block uses expires epoch, which sends Cache-Control: no-cache, while the image and font block uses expires 30d. Nginx sends ETags for static files automatically. Using the expires directive rather than add_header matters, because any add_header in a location block stops it inheriting server-level headers such as Content-Security-Policy and HSTS.

How can I check that browser caching with revalidation is working?

You can request a script's headers with curl -sI followed by the file's address. The response should show cache-control: no-cache, an etag, and your usual security headers, confirming they have not been lost. Images should still show max-age=2592000, the 30-day cache. Once this is confirmed, the ?v= tags can be removed from your pages. Bear in mind that visitors who downloaded a file under the old 30-day rule will keep that copy until it expires, so the change only applies to copies fetched after the new headers are in place.

💬 Got a problem?

aiwebpageseo.com is a data-driven SEO and AEO platform providing a free suite of technical website tools.