Invite-only beta · 9 free places left · Managed hosting on AWS

AceMedia HostTalk to us

CACHING · 8 NOVEMBER 2026

Cache the page, not the reader: a Redis page cache on a busy news site

Two rules kept a Redis page cache safe on a sports betting news site: nothing that belongs to one reader goes into cached HTML, and anything that changes with time carries its time window in its cache key. A newsroom-aware "most popular" list shows both at work.

A news site with a sudden spike in readers is the textbook case for a full page cache. Store the finished HTML, serve it to the next thousand visitors without touching PHP or MySQL. On the site I have been working on, a sports betting news publisher running WordPress, that cache is a Redis page cache I maintain.

Full page caching is easy to switch on and easy to get wrong. The wrong versions all come down to the same two mistakes.

Rule one: nothing per-reader in cached HTML

This site has a lot of per-reader state. Readers choose an odds format, a default stake, light or dark mode, a reduced motion option and a dyslexia-friendly font. Some are signed in. Many have something in a bet slip.

If any of that is rendered into the HTML by PHP, the first reader to load a page decides what everyone else sees. One person picks decimal odds and dark mode, and the next thousand get decimal odds and dark mode.

So the server always renders the neutral version: default odds format, light scheme, no slip, signed out. Every preference is applied in the browser, from the reader's own stored settings, before the first paint. A small script in the head reads the settings and sets classes on the root element, so there is no flash of the wrong theme. Prices are rendered in the site's default format and converted to the reader's chosen format on their device.

The test I use: load a page as a reader with every preference changed, then load the same page in a private window. If anything from the first visit shows up in the second, it leaked into the cache.

Rule two: tie time to the key

The second mistake is caching something that changes with time and hoping an expiry catches it. "Cache for an hour" works until a page is cached at 23:59 and keeps showing yesterday for an hour.

The fix is to put the time window into the cache key itself. A list that changes every ten minutes is cached under a key that includes the current ten-minute slot. When the slot changes, the key changes, and the old entry is never read again. It does not need purging. It simply stops being asked for, and expires on its own.

A most popular list that follows the newsroom

The "most popular" list in the sidebar shows both rules at work. The old version ranked total views over the last day. That sounds reasonable, but at nine in the morning it showed what was popular the previous evening, while the newsroom was publishing the day's stories.

The new version follows the newsroom's day:

  • Views are logged per post in ten-minute slots, as well as per day.
  • Overnight, from midnight until the newsroom's day has started and the first story of the day has had time to collect readers, the list ranks the last 24 hours and says so.
  • During the day it ranks the last hour, widening to three hours and then the whole day if there are not yet enough views to rank sensibly.
  • Stories published today enter as soon as they have views. If the list is short, it is filled with the newest stories.
  • The list writes its own heading, so the reader always knows which window they are looking at.
Ranking on ten-minute slotsRanking window: the last hourNowOlder slots are kept for two days, then prunedCache key carriesmode · window ·current slot
Illustrative counts. The window slides one slot at a time, and each step gets a fresh cache key.

Every cache key carries the mode, the window and the current slot, so the list can never serve yesterday. The rendered HTML is compressed before it is stored, which keeps it comfortably under the object cache's value size limit.

A live block inside a Redis page cache

There is one more problem. If the whole page is cached for, say, thirty minutes, the popular list inside it is frozen for thirty minutes too, however clever its own cache is.

The page cache supports live blocks: named blocks that are rendered again on every hit, even when the rest of the page comes from the cache. The popular list registers as one. Because its own output is cached per slot, re-rendering it costs one Redis read, not a database query. The same mechanism keeps the bet banners' prices and links working on a cached page.

Where live blocks are switched off, the plugin caps the page's lifetime instead, so the list is never more than one slot stale.

Crawl hygiene comes along for free

A cache tends to expose sloppy addresses, because every distinct URL becomes a distinct cache entry. The same work tidied up several things search engines care about too:

  • pages that are finished, such as a settled results day, are cached in full, while days still in progress are not;
  • preview flags bypass the block cache, so a preview never ends up stored for everyone;
  • each page outputs exactly one canonical and one robots tag;
  • publishing a post rebuilds the calendar's source caches and refreshes the affected pages, rather than waiting for expiry.

Checklist

  1. List every piece of per-reader state. Make sure none of it is rendered by PHP into a cacheable page.
  2. Apply preferences in the browser before paint, so there is no flash.
  3. Put time windows into keys rather than relying on expiry.
  4. Compress large fragments before storing them.
  5. Let the few genuinely live parts of a page re-render cheaply, and cache everything else.
  6. Test with two browsers that share nothing.

I have written before about Redis timeouts and keeping shared Redis separated by site. This is the other half: deciding what is safe to cache at all.

More hosting tipsTalk about your hosting

INVITE-ONLY BETA

Tell us about your site.

Nine free hosting beta invites remain for open-source projects, charities, community organisations and other public-benefit work. Tell us what you’re building; we’ll review the fit and reply personally. Fields marked * are required.

Please do not send passwords or private customer data. We’ll use these details to reply. Privacy notice.

Prefer email? Write to [email protected].