# Cache preloading: never serve a cold page again

Your deploy finished at 8:47. The deploy script flushed the page cache, the way deploy scripts do, and at 9:02 the first real visitor of the day lands on your pricing page. They get the slowest version of it that exists: PHP boots, every plugin loads, every database query runs, and the page is built from scratch while they look at a blank tab.

That visitor did you a favour. Their request rebuilt the cache entry, so the next person gets the fast version. But nobody chose them for the job. Page caches recover on the backs of real visitors, one slow request at a time, and on most sites that recovery takes far longer than you would guess.

This post is about the gap between a cache clear and a warm cache: what a cold cache actually costs, why it hits the pages you least expect, how much of the problem good cache architecture already covers, and how preloading closes the rest, so that the first visit is as fast as the thousandth.

## What a cold cache costs on the first visit

A cache hit and a cache miss are not two speeds of the same thing. They are two different programs. On a hit, MilliCache finds the stored HTML in Redis inside `advanced-cache.php`, sends it and exits, before most of WordPress has even loaded. On a miss, the request falls through to WordPress and everything runs:

- PHP boots the whole application: core, every active plugin and the theme
- The database answers every query the page needs
- The template renders, the output is stored for next time, and only then does the first byte leave the server

The visitor feels every step of that as time to first byte. On our WooCommerce test shop, running on a local machine with nothing else going on, the shop page took between 47 and 80 milliseconds as a miss and 4 milliseconds as a hit. On a production shop the gap is wider: the MilliCache Header Inspector on propain-bikes.com measured the same page at 1,362 milliseconds as a miss and 109 milliseconds as a hit. No amount of frontend optimisation touches that number, because the browser is waiting for the server to start talking.

This is not a MilliCache quirk. Every full-page cache has this shape. A page cache does not make pages fast, it makes the second request fast. Someone always pays for the first one. The only question is who: a visitor, a crawler, or a background process you control.

## The long tail never warms itself

After a full flush, your homepage is cold for about a minute. It gets enough traffic that somebody rebuilds it almost immediately, and everyone after them is fine. Popular pages warm themselves. That is why cold caches feel like a non-problem when you test your own site: you always test the popular pages.

Traffic follows a long tail, though. The homepage gets a visit every minute; a documentation page from 2022 gets one every few days. After a flush, the popular third of your site is warm within the hour, and the rest stays cold until someone happens to want it. On a quiet page, "the next visitor pays for the render" means the person who pays is hours or days away, and quite possibly the only person who will see that page all week.

A cache lifetime makes it worse. If a page gets one visit per lifetime, every visit is a first visit: the entry expires before anyone asks for it again, so its hit rate is zero. Your cache report says 95 percent hits overall, and it is telling the truth, but that average is carried entirely by the popular pages. The long tail of your site is always cold.

On those pages, the "next visitor" is very often Googlebot, or increasingly an AI agent. Crawlers spend most of their time in your long tail by design, so they see your worst time to first byte, not your average. Google [documents](https://developers.google.com/search/docs/crawling-indexing/large-site-managing-crawl-budget) that a site which answers quickly gets crawled more. That is a modest effect, not a ranking penalty, but it means the pages you most need discovered are the ones being served cold.

## What good architecture already covers

Before reaching for a preloader, it is worth knowing how much of this problem disappears with things MilliCache does anyway, because a preloader should be the last line of defence, not a bandage over wasteful clearing.

**Targeted invalidation keeps the damage small.** A cache entry knows what content it contains, so editing a post clears the handful of pages that show it instead of the whole cache. A cache that is never wiped rarely gets cold at scale.

**Grace turns expiry into a non-event.** When an entry's lifetime runs out, MilliCache does not delete it. Within the grace period, the next visitor is served the old copy instantly while the page is rebuilt in the background. Expired is not cold, even in the long tail: the once-a-week visitor gets a fast, slightly dated page, and the cache heals behind them.

**Expiring instead of deleting** gives a manual clear the same treatment. `--expire` does not remove anything. It marks the pages as out of date, so the next visitor to each one still gets the old copy instantly while a fresh copy is built behind them. A full clear stops meaning "everyone waits" and starts meaning "everyone gets yesterday's page once":

 terminal 

 ```
 1wp millicache clear --expire

```

Between those three, the cold-cache problem shrinks to two hard cases: entries that were genuinely deleted, because serving the old copy would have been wrong, and pages that were never cached in the first place. For those, someone still pays for the first render. Preloading decides who: a background process instead of a human.

## Preload the cache from your sitemap

The simplest fix is to stop waiting for visitors and request the pages yourself. Your XML sitemap is already a machine-readable list of every URL you care about, so the most direct form of preloading is a crawl of your own sitemap. Each request either finds a cold page and warms it, or finds a warm page and costs nothing.

You can put this together with curl and a sitemap parser, but the edges are sharp: sitemap indexes that point to other sitemaps, multilingual sites with one sitemap per language, and the difference between hammering your server and pacing the work. MilliCache Pro ships it as a WP-CLI command that handles those edges:

 terminal 

 ```
 1# Preview which URLs would be preloaded, without requesting them
 2wp millicache preload --dry-run
 3
 4# Fetch every sitemap URL and warm the cache, with a progress bar
 5wp millicache preload

```

The dry run on our test site shows what it found and where:

 terminal 

 ```
 1$ wp millicache preload --dry-run
 2Fetching sitemap from https://wp.test/sitemap.xml...
 3Found 21 URL(s) in sitemap(s):
 4  https://wp.test/
 5  https://wp.test/shop/
 6  https://wp.test/pricing/
 7  https://wp.test/blog/2026/07/07/faster-wordpress-in-five-minutes/
 8  https://wp.test/blog/category/allgemein/
 9  https://wp.test/blog/author/philipp/
10  …

```

Because it is a CLI command, it slots into the places where caches actually get cleared. A deploy step that used to be "clear the cache and hope" becomes:

 deploy script 

 ```
 1wp millicache clear
 2wp millicache preload --async

```

The clear is instant, the queue drains in the background, and by the time post-deploy traffic ramps up, the sitemap's worth of pages is warm again. It does targeted work too: `wp millicache preload --uri=/pricing/` rewarms a single page after a targeted clear.

None of this needs a terminal. The same full run is one click in WordPress: **Preload Full Cache** sits in the menu at the top of the MilliCache settings screen and in the Cache Entries tab, and the command palette (⌘K, type `preload`) offers it as *MilliCache: Preload full cache*. The CLI form exists for the places where nobody is clicking, like a deploy script.

![WordPress command palette listing MilliCache actions, with "MilliCache: Preload full cache" highlighted](https://www.millipress.com/content/uploads/2026/09/millicache-pro-preload-command.png)The same full preload from the command palette (⌘K), no terminal needed.On-demand preloading has one weakness: it only runs when you remember to run it. Deploys are easy to hook. The editor who clicks Update on a post at 3 p.m. is not.

## Event-driven, not cron-driven

The cache does not go cold on a schedule. It goes cold at specific moments: a post is published, an entry is deleted, someone clears everything. So the better trigger for warming is not a timer but those events themselves. That is how MilliCache Pro's Cache Preloading module works, and there is no schedule to configure:

| Event | What gets preloaded |
|---|---|
| You publish or update a post | The post itself and the pages that list it: the blog index, and its category and date archives, worked out from the post's cache flags |
| A cache entry is deleted | That entry's own URL is queued again, unless the post behind it has been trashed |
| A full cache clear | Every URL from your XML sitemaps |
| Preload Full Cache (manual) | Every URL from your XML sitemaps |

The first two rows are a direct payoff of the flag system. Because every cache entry knows what content it contains, the preloader can work backwards from an eviction to the URLs that need rewarming. Here is what one post update on our test site put in the queue:

 preload queue after saving one post 

 ```
 1https://wp.test/news/
 2https://wp.test/blog/category/performance/
 3https://wp.test/blog/2026/07/07/faster-wordpress-in-five-minutes/
 4https://wp.test/blog/2026/07/07/
 5https://wp.test/blog/2026/07/
 6queued: 5

```

The blog index, the post's category, the post itself, and its day and month archives: exactly the pages the edit had just cleared. They are warm again before a visitor notices they were gone. The eviction and the rewarm are two halves of one motion.

![MilliCache Pro Cache Entries header showing 116 pages, 4 MB cache size, 17 stale entries and a spinner reading Preloading: 1 URLs remaining](https://www.millipress.com/content/uploads/2026/09/millicache_status-preloading.png)While the queue drains, the Cache Entries browser shows how many URLs are still to go.It is built to be gentle with the server. A URL is queued once, no matter how many events touch it. The work runs in the background in small batches, and each page is requested the way a visitor would request it, so it lands in the cache through the normal render path. If you run the Edge Cache module as well, the two coordinate, so a page is never warmed through a stale CDN copy.

## Sitemap discovery you don't configure

Full preloads need a list of URLs, and the module gets it the way the sitemap protocol intends: it reads your robots.txt, follows every `Sitemap:` line, and expands sitemap indexes. Whatever generates your sitemaps, WordPress core, The SEO Framework, Yoast or Rank Math, is picked up automatically, and discovery happens fresh on every run, so a sitemap that appears next month is included next month without touching a setting.

The case that usually breaks warmers is a multilingual site. Polylang with The SEO Framework, for instance, publishes one sitemap per language (`/sitemap.xml`, `/en/sitemap.xml`) with no index linking them; only robots.txt mentions both. Discovery that starts from robots.txt finds every language.

A manual Sitemaps setting exists for the edge cases: a static robots.txt without `Sitemap:` lines, or a sitemap you do not want warmed. And one WordPress gotcha is worth knowing: with Settings → Reading → "Discourage search engines from indexing this site" switched on, WordPress publishes no sitemap at all, so a full preload has nothing to read. The Status tab says so instead of silently doing nothing.

  

 

 Tip:The background queue is worked off on page loads or through WP-Cron. On a quiet site (staging, or a launch before the marketing goes out) point a real cron job at WP-Cron, so preloads finish promptly rather than waiting for visitors who have not arrived yet.

 

 

 

## When preloading everything backfires

Preloading has a bad reputation in some corners of WordPress, and it was earned by naive crawlers. A preloader that fires the moment the cache clears and requests every URL as fast as it can is, from your server's point of view, a traffic spike arriving at the exact moment the cache is empty. Every one of those requests is a full render. That is the worst possible timing, and on shared hosting it is how a "performance plugin" takes a site down.

There is a quieter waste too. A sitemap with 10,000 URLs where 9,000 pages get no visits this month means a naive full preload spends nine tenths of its renders, and the memory to store the results, on pages nobody will ask for before they expire again. Preloading everything is not automatically right. What earns its keep is rewarming the pages that were just cleared, because those demonstrably had traffic, and being deliberate about whether the rest is worth the render.

A good preloader is defined as much by restraint as by reach: one queue entry per URL, batches in the background, nothing that grace already covers, and full-sitemap refills as a deliberate event rather than a reflex.

## Fewer clears, fewer rewarms

Preloading and targeted invalidation are two ends of the same lever. Invalidation decides how many pages go cold; preloading decides how long they stay that way.

They multiply. If an edit clears six pages instead of two thousand, event-driven rewarming has six renders to do and is done in seconds. If every edit flushes everything, even a good preloader has to re-render your whole site several times a day, which is exactly the load a cache exists to prevent. Shrink the damage first, then rewarm what is left. A site that does both spends nearly its whole life warm, and the first visit stops being a special case.

## Try it

**MilliCache** works the invalidation side: flag-based clearing keeps most of your cache warm through every edit, and grace with `--expire` means an expired page is served fast while it is rebuilt. Set those up first; they decide whether preloading is a safety net or a crutch.

**MilliCache Pro** adds the warming side: Cache Preloading rewarms cleared pages event by event, refills the whole cache from your sitemaps after a clear, and ships the `wp millicache preload` command for deploy scripts and cron. The Cache Entries browser lets you watch the cache refill, and Detailed Metrics shows the payoff: a hit rate that no longer dips after deploys and publishes.

[ Compare MilliCache and MilliCache Pro ](/millicache-pro/#compare)