PLAINFINCH learn by building

Browser caching

You will tell browsers which files they may keep, so a returning visitor loads the page without downloading them again.

What does the browser keep between visits?

Every browser has a cache: a folder of files it downloaded recently, with a note of where each came from and what the server said about keeping it. When a page asks for /css/club.css again, the browser looks there first. If it has a copy that is still fresh, it uses it without touching the network at all.

The server decides what fresh means, through the Cache-Control response header. The browser follows those instructions. Your .htaccess file is where you write them on an Apache server, and it sends them with every response that matches.

The Riverside Bird Club page is a good candidate. Members open it every morning to see the latest sightings. The stylesheet, the script and the kingfisher photo change a few times a year; the sightings list in the HTML changes daily. Those two kinds of file want different rules.

What does the second visit look like now?

Open the Waterfall and switch to Second visit. The simulator pretends the same phone comes back one day later. Every row downloads at full size: 176 KB again, and the photo paints at about 1.9 seconds, exactly like the first time.

That is because the .htaccess says Header set Cache-Control "no-store" for everything. no-store means do not keep this at all. Someone copied it from an old forum answer to stop people seeing out-of-date pages, and it works, at the price of every visit being a first visit.

The Vitals view shows the first and second visits side by side. Keep it open while you edit, and watch the second-visit column.

What do max-age, no-cache and no-store mean?

max-age=604800 means the browser may use its copy for 604,800 seconds, which is one week, without asking the server. During that time the file costs nothing: no request, no round trip, 0 KB. After it, the copy is stale and the browser checks before using it.

no-cache is the most misread value. It does allow the browser to keep the file. It means check with the server before each use. The browser sends a short request saying which version it has, using the ETag or Last-Modified the server gave it, and the server replies 304 Not Modified with no body if nothing changed. That costs one round trip and a few hundred bytes.

no-store is the strict one: never keep a copy. Use it for pages with private data, such as an account page showing a member's address. For a public stylesheet it is pure waste.

max-age=604800   keep for a week, no questions asked
no-cache         keep, but check with the server each time
no-store         never keep a copy
FigureThree branches for the kingfisher photo on a second visit: no-store downloads all 140 KB again, no-cache sends a 0.3 KB check and waits a round trip, max-age=604800 uses the kept copy for 0 KB.Three branches for the kingfisher photo on a second visit: no-store downloads all 140 KB again, no-cache sends a 0.3 KB check and waits a round trip, max-age=604800 uses the kept copy for 0 KB.
Only max-age lets the phone skip the network. no-cache still keeps the file, but every use waits for a 304 from the server.

How do you set different rules for different files?

Apache's <FilesMatch> block applies the directives inside it only to files whose names match a regular expression. The pattern \.(css|js|webp)$ matches any name ending in .css, .js or .webp. The backslash makes the dot a real dot, and the $ anchors the match to the end of the name.

Put the general rule first, without a block: Header set Cache-Control "no-cache" for everything, which covers the HTML. Then add the block for the files that change rarely. Inside it, Header set replaces the general value for those files.

The Header directive needs mod_headers, which nearly every host has. ExpiresByType from mod_expires is an older way to say the same thing by content type, for example ExpiresByType image/webp "access plus 1 year". It still works, and the simulator reads it, but Cache-Control is clearer because you can see the exact value being sent.

Header set Cache-Control "no-cache"

<FilesMatch "\.(css|js|webp)$">
  Header set Cache-Control "max-age=604800"
</FilesMatch>

How long should a file be cached?

Long enough to cover the gap between visits, and short enough that a change reaches people when you need it to. The simulator's second visit comes a day later, so anything under 86,400 seconds is already stale by then. Ten minutes, a common default, helps someone clicking between pages in one sitting and no one else.

A week is a reasonable start for files you edit by hand. The cost is real, though: if you fix a typo in club.css, a member who visited yesterday keeps the old stylesheet for up to six more days. The next lesson removes that trade-off by giving each version of a file its own name, which lets you cache for a year safely.

The HTML page itself should almost always be no-cache. It is small, it links to everything else, and it is where new content appears. The simulator always fetches the page again on the second visit, whatever its header says.

How much faster is the second visit?

After the change, the second-visit waterfall shows the stylesheet, script and photo tagged from cache, each finishing in a couple of milliseconds. The only network request is the HTML page, under 1 KB. Total second-visit download goes from 176 KB to about 0 KB.

The kingfisher photo paints at about 0.8 seconds instead of 1.9. Most of what is left is the HTML round trip and the server's think time; the browser can lay out and paint as soon as the page arrives, because everything it links to is already on the phone.

The first visit doesn't change at all: 176 KB, largest paint at about 1.9 seconds. Caching rewards people who come back. For a club page read by the same members every week, that is most visits.

How do you see caching in Chrome DevTools?

Open the Network panel and make sure Disable cache is unticked; it is often left on while developing. Load the page, then load it again by clicking a link to it or pressing Enter in the address bar. Files served from the cache show (disk cache) or (memory cache) in the Size column, and a 304 shows as a small transfer with status 304.

Click a row and look at Response Headers to see the exact Cache-Control value the server sent. If it isn't what your .htaccess says, the file may be served by a different rule, a CDN, or a host setting that overrides yours.

A normal reload behaves differently from a fresh visit: browsers check the page itself and may revalidate more. For a fair test, follow a link or open the address in a new tab. Lighthouse also flags static files with short cache lifetimes.

What do AI answers get wrong about caching?

The most common mistake is reaching for no-store or a pile of headers like Pragma: no-cache and Expires: 0 to fix an out-of-date page. That stops caching of everything, including files that never change. Pragma is an HTTP/1.0 header with no effect on modern browsers when Cache-Control is present.

The opposite mistake is just as common: a one-year max-age on every file, including the HTML and stylesheets with fixed names, with no plan for how a change will reach visitors. That is how people end up asking members to clear their cache.

AI answers also tend to say that no header means no caching. Real browsers may guess a lifetime for a file with no Cache-Control, often a tenth of the time since it last changed. The simulator checks with the server every time instead, which is the cautious reading. Set the header yourself so nothing is left to guessing.

Field notes

  • The browser keeps recent files and reuses fresh copies without asking.
  • max-age sets how many seconds a copy stays fresh.
  • no-cache means check with the server first; no-store means never keep it.
  • Use <FilesMatch> to give different file types different rules.

Easy to get wrong

no-store throws away files that never change. no-cache keeps up-to-date pages and costs only a quick check.

Ten minutes is over before most people come back. Pick a lifetime longer than the usual gap between visits.

A pattern only matches the endings you list. Files that slip through get the general rule, often no-cache.

no-cache lets the browser keep the file and revalidate it with a cheap 304. no-store is the value that forbids keeping it.

Questions people ask

Can I set caching for files from other sites?

No. A font or widget from another site carries that site's headers, and your .htaccess has no say. You can choose whether to use them at all, and the third-party scripts lesson covers that choice.

Do I need ExpiresByType as well as Cache-Control?

No. When both are present, browsers follow Cache-Control. ExpiresByType is fine on older setups; use one approach so the rules stay easy to read.

What about private pages, such as a member's bookings?

Send Cache-Control: private, no-store for anything personal. private stops shared caches such as a CDN keeping a copy, and no-store stops the browser keeping one on a shared computer.

Why does the simulator fetch the HTML on every second visit?

It models the common, sensible setup where pages are no-cache. A real browser would honour a long max-age on HTML too, which is one reason not to give one to your pages.

Keep going