Who this is for. Theme developers and reviewers. Afterwards you can say why a given page is or is not stored, spot the template habits that break caching, and run the diagnostic when a page answers miss every time.
A page can fail the cache in two directions: it is never stored, so every visitor waits for a render, or it is stored when it should not be, so visitors see the wrong thing. The second is more harmful and harder to notice.
The rules a render must pass
After a page renders, the platform stores it only if all of these hold:
- The environment is not a development environment. Nothing overrides this one.
- No platform route, and no visitor language cookie, switched caching off for this request (below). Nothing overrides this one either.
- The template did not call
preventCaching(), unless it also calledenforceCaching(), which skips this rule and every rule after it (the other controls). - The request was a GET or a HEAD.
- The response is a 200. Redirects, 404s and errors are never stored.
- No visitor was signed in when it rendered (authenticated requests).
- The request did not ask for JSON with
Accept: application/json. - The path and query string match none of the patterns in Cache settings › Response cache exclusions.
Deliberate causes
- Your
preventCaching(), for a response that genuinely differs per visitor (preventCaching() and fragments). - The exclusions setting. One regular expression per line, matched against the path and query string. The platform wraps each line in
/delimiters, so escape any slash in the pattern:^\/events\/calendar, not^/events/calendar. A pattern stops new copies being stored; it does not remove copies already stored, so clear the cache after adding one. - Routes that prevent caching themselves: the cart page, the single-page checkout and its payment processing step, the invoice payment return page, the sitemap, e-mail confirmation, passkey prompts, the Documentation module's pages and any revision preview. A search page is not on the list: it is stored once per declared query parameter like any other page, and only a search that failed (the search engine could not answer) is kept out of the cache, so an outage is not stored as "no results" (caching a search page).
- A visitor's language cookie. When a visitor has chosen a language through the
cdna_localecookie and it differs from the site's language setting, their render is not stored, because the cache key cannot see cookies.
Accidental causes
These come from ordinary template code and produce no error:
preventCaching()in a shared partial. A call in the header, the footer or a component used site-wide switches the cache off for every page that includes it. Search the templates for the call and check what includes each file that has one.- A template redirect on a common path.
$_response->redirect()answers 302 by default, which is never stored, so a page that redirects "just in case" renders every time. - A 404 that should be a 200. A not-found answer is rendered for every request: typically a listing whose page number ran past the end, or a custom not-found template doing real work.
- A cookie set in a template.
$_response->setCookie()does not stop the page being stored. The stored copy keeps its content and a short list of headers (content type and length,ETag, the content security policy and the CDN tags), andSet-Cookieis not one of them. Only the visitor whose request rendered the page gets the cookie; every later visitor gets the page without it. Set cookies from a fragment or from JavaScript, never from a cacheable page. - Personal output on a public page. A render for a signed-out visitor that prints anything about that visitor, such as a guest cart count read through
$_cart, a cookie value or a geolocated greeting, is stored and served to everyone (the cart count is wrong or stale).
Query parameters
The page cache key is the hostname and the path, plus only the query parameters the response declared. A template declares a parameter in one of two ways: by reading it through $_request->getParameter('name'), or by naming it with $_response->addCacheVaryParameter('name') (or several with setCacheVaryParameters([...])). Nothing else declares one: not $_request->getParameters(), not Smarty's $smarty.get, and not a controller that read the parameter for you. Two failures follow:
- An undeclared parameter is invisible. If
/events?month=6is rendered first and the template readmonthsome other way, that render is stored under/eventsand served for every month, and for/eventsitself. Module listings and search pages read their parameters in the controller, so their templates must declare the parameters they depend on, the page number included (caching a search page). - A declared parameter you did not need splits the cache. Every distinct value is a separate stored copy, so a template that reads
utm_sourceor a click id stores one copy per campaign link and seldom gets a hit. Read tracking parameters in JavaScript, not in the template.
Declare a parameter by reading it, even when you only branch on it: