I deployed, and the old page kept showing for an hour
Oct 25, 2026 Β· Operations Β· νκ΅μ΄
After deploying a new version, the old game list kept showing for about an hour. The code had been deployed correctly. The cache was the problem.
This site runs on Firebase Hosting. A browser decides how long to keep using a file without asking again from the Cache-Control header in the response. With max-age=3600 it uses its saved copy for an hour without asking the server. If a deploy changes the file in that time, the browser has no idea. So a site whose pages change needs to choose this value carefully.
In Firebase Hosting you can set response headers per address pattern under headers in firebase.json. To give HTML, JS and CSS a cache setting, I wrote this.
{ "source": "**/*.@(js|css|html|glb)",
"headers": [{ "key": "Cache-Control", "value": "public, max-age=0, s-maxage=604800" }] }
But this site turns on cleanUrls, so it uses addresses without an extension, such as /ko/games/cloudhop. The file that actually opens is cloudhop.html, but the pattern is chosen by the address that was requested, not the file name. /ko/games/cloudhop has no .html, so it never matched the rule above.
Responses that match no rule get Firebase's default cache. The default is one hour (max-age=3600). People's browsers keep using the old page for an hour. The real trouble was that the most opened pages (the home page and the game pages) are exactly these extensionless addresses.
It was unexpected, so it took a long time to even notice. It may not sound like a big problem, but if what you just deployed only reaches users later, it can cause all sorts of trouble.
I added a rule that catches extensionless addresses separately. Every address without a dot (.) gets the same header.
{ "regex": "^[^.]*$",
"headers": [{ "key": "Cache-Control", "value": "public, max-age=0, s-maxage=604800" }] }
This rule matches / and /ko/games/cloudhop, which have no dot, and doesn't match /portal.js or /img/logo.png, which do. I also merged the home-only rule I had before into this one.
Header rules are chosen by the requested address, not by the name of the file being sent. On a site with clean URLs, a rule written by extension can miss most of your pages. After changing headers, it is safest to check the response headers of a real page address once.
References
- MDN, Cache-Control: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
- Firebase, Hosting configuration (headers, cleanUrls): https://firebase.google.com/docs/hosting/full-config
- Firebase, Manage cache behavior: https://firebase.google.com/docs/hosting/manage-cache
More posts
- Why there is always a cloud within reach in Cloud Hop Oct 25, 2026
- Why the crumbling clouds never sit on the path Oct 25, 2026
- I removed the background and a pink edge was left Oct 25, 2026
- Three tries in one day to decide what not to deploy Oct 25, 2026
- A ten-line FPS counter for browser games Oct 25, 2026
