therealworld / staticcache-module
Certain OXID7 controllers create static caches and may display them.
Package info
bitbucket.org/therealworld/staticcache-module
Type:oxideshop-module
pkg:composer/therealworld/staticcache-module
Requires
- php: ^8.3
- ext-json: *
- oxid-esales/oxideshop-ce: >=v7.4
- therealworld/scheduler-module: >=v2.2
- therealworld/tools-plugin: >=v3.5
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- v3.0.5
- v3.0.4
- v3.0.3
- v3.0.2
- v3.0.1
- v3.0.0
- v2.x-dev
- v2.1.16
- v2.1.15
- v2.1.14
- v2.1.13
- v2.1.12
- v2.1.11
- v2.1.10
- v2.1.9
- v2.1.8
- v2.1.7
- v2.1.6
- v2.1.5
- v2.1.4
- v2.1.3
- v2.1.2
- v2.1.1
- v2.1.0
- v2.0.9
- v2.0.8
- v2.0.7
- v2.0.6
- v2.0.5
- v2.0.4
- v2.0.3
- v2.0.2
- v2.0.1
- v2.0.0
- v1.2.12
- v1.2.11
- v1.2.10
- v1.2.9
- v1.2.8
- v1.2.7
- v1.2.6
- v1.2.5
- v1.2.4
- v1.2.3
- v1.2.2
- v1.2.1
- v1.2.0
- v1.1.7
- v1.1.6
- v1.1.5
- v1.1.4
- v1.1.3
- v1.1.2
- v1.1.1
- v1.1.0
- dev-master
This package is auto-updated.
Last update: 2026-10-04 13:56:08 UTC
README
description
Stores the rendered html of a page and serves the next visitor from that copy, so the shop answers without running the controller, the templates or their database queries. Logged-in visitors and visitors with a basket still get the cached page — the parts that must not be shared are replaced with placeholders and reloaded over ajax.
New oe-console commands
- trw:staticcache:prune [--shop-id=N] [--dry-run]
- trw:staticcache:status [--url=...] [--lang=N] [--explain]
The commands live in this module, not in the clirun-plugin: only the module knows how its
cache is stored, and this way the plugin does not need the module as a dependency. They are
registered through the module's services.yaml and appear in oe-console as soon as the
module is installed and active.
What is cached, and what is not
A page is served from the cache unless the controller is excluded or the request carries
an fnc parameter. A page is stored only when it additionally comes from an anonymous
visitor with an empty basket, answers with http 200, is not a redirect, contains a closing
document tag, and the volume still has room.
Never cached: everything under account*, the basket, the user, payment, order and thank-you
controllers, plus the controllers active modules exclude themselves: through the
CacheExcludedControllerContributorInterface decoration chain of the tools-plugin (base class
AbstractCacheExcludedControllerContributor, examples: the captcha endpoints, the newsletter
web view, the withdrawal form). An exclusion lives in the module's services.yaml and stops
applying on its own when the module is deactivated. There is no setting for it: a shop that has
to exclude a core or third party controller adds a small contributor of its own.
Every response says what happened
The X-TRW-StaticCache header states the outcome of each request, which is what makes the
cache diagnosable at all — and what lets a warmup verify in a single request instead of two:
| header | meaning |
|---|---|
miss, stored | rendered and written to the cache |
cache, age=42s | served from the cache |
cache+hydration, age=42s | served from the cache, dynamic widgets reloaded over ajax |
cache+token, cache+hydration+token | the visitor's own csrf token was injected |
bypass, reason=admin | an admin is looking, always live |
miss, reason=controller | this controller is never cached |
miss, reason=user / basket | not an anonymous, empty-basket visitor |
miss, reason=function | the request carries fnc |
miss, reason=status / redirect | not a plain 200 |
miss, reason=nohtml | a fragment, not a whole page |
miss, reason=diskspace | below the configured free space minimum |
miss, reason=savefailed | the entry could not be written |
curl -s -D - -o /dev/null 'https://example.org/a-page/' | grep -i x-trw
Note that OXID counts curl as a search engine (aRobots in config.inc.php), so a plain
curl call sees the bot variant of the page. Pass -A 'Mozilla/5.0 ...' for the visitor view.
The cache key
Built from the shop id, the protocol, the language, the request path and those query parameters that are allowed to matter. It deliberately does not contain the session, so one entry serves everybody.
Query parameters: a whitelist, not a blacklist
aTRWCacheKeyAllowedParams lists the parameters that may take part in the key. Anything not
listed is stripped from the key — the request is still cached and still served, it just
no longer creates an entry of its own.
This is the important setting of the module. With an ignore list alone, every parameter nobody thought of — a new tracking network, an affiliate id, a typo, a bot experimenting — silently multiplies the cache: one crawl once produced 901,349 keys for 23,811 pages and filled the volume.
The price is that the direction of failure flips. A parameter that really does change the
page and is missing from the list will make the shop serve the wrong content, where before it
only wasted disk. A module that introduces such a parameter therefore declares it itself:
through the CacheKeyParamContributorInterface decoration chain of the tools-plugin (base
class AbstractCacheKeyParamContributor, example: libriid in the libri-module). Those
parameters do not appear in the setting, but apply on top of it while the module is active —
and leave the key again on their own when it is deactivated. Leaving the list empty restores the old
behaviour (ignore list only) — that is the way back if a shop turns out to depend on
something nobody listed.
aTRWCacheKeyIgnoredParams still applies afterwards and keeps session and campaign
parameters out of the key even if someone puts a * in the whitelist.
Use trw:staticcache:status --url=... --explain to see what the key building does to a
concrete url:
Url : https://example.org/a-category/?pgNr=2&utm_source=news&whatever=1
State : warm (expires in 00:59:42)
normalized : /a-category/?pgNr=2
params kept: pgNr
dropped : utm_source, whatever
Cache key : 923a5d6205ebeb2b08c5460c390a181c
Housekeeping
Symfony's filesystem cache only removes an expired entry when the same key is read again. A url a bot asked for once and nobody ever asks for again therefore stays on disk forever, which is exactly the shape of the traffic a page cache sees. Without a prune the directory only grows.
trw:staticcache:prune
Drops expired entries and enforces the size limit, evicting the entries with the shortest
remaining lifetime until the cache is back to 90% of the limit. Deletes without an extra
flag — an expired entry is expired, and a command meant for cron must not need one; use
--dry-run to look first.
vendor/bin/oe-console trw:staticcache:prune --dry-run
vendor/bin/oe-console trw:staticcache:prune --shop-id=1
The scheduler task Prune the page cache does the same thing nightly (0 4 * * *). It is
installed inactive like every discovered task — switch it on in the backend.
trw:staticcache:status
Reports the directory, the number of entries, their size, the expiry range and the free disk
space; with --url it says whether one page is warm and, with --explain, how its key is
built. This is also the only honest test that a warmup works: cold before the request, warm
after it.
Options
Cache duration
- iTRWCacheLifetime: how long an entry stays valid, in seconds (default 3600)
Caching rules
- aTRWCacheKeyAllowedParams: the whitelist above. Empty = old behaviour
- aTRWCacheKeyIgnoredParams: parameters removed from the key after the whitelist;
a trailing
*matches by prefix
Admin bypass
- bTRWBypassCacheForAdminUser: a frontend user with admin rights always sees the live page and never writes to the cache. Costs one database query per logged-in request
- bTRWBypassCacheForAdminCookie: same for any browser carrying a backend session cookie. Free (no database access) and covers the usual workflow. The cookie is not validated — it only bypasses the cache and grants no rights
Size and disk limits
- iTRWCacheMaxSizeMb: upper bound per shop, 0 = none. Enforced by the prune run only
- sTRWCacheMinFreeDisk: below this much free space the cache stops writing but keeps
serving (default
2G; accepts2G,500M,5%or plain bytes; empty switches it off). If free space cannot be measured, writing continues — a broken measurement must not switch the cache off
Html minification
- bTRWMinifyHtml: minify the whole html output, cached or not.
<script>,<style>,<textarea>and<pre>are protected
Dynamic hydration
- bTRWDynamicHydration: serve cached pages to logged-in visitors as well and reload the personalised widgets over ajax
- aTRWDynamicWidgets: which
data-trw-dynamicmarkers to hydrate
See docs/DYNAMIC_HYDRATION.md for what a theme has to provide, and which widgets cannot be
hydrated.
Where the cache lives
<sCompileDir>/static_cache/<shopId>/ — its own directory, next to the compiled templates
rather than inside them, so a template deployment and a page cache purge no longer take each
other down and the page cache can be cleared, pruned and measured on its own.
trw:clear:cache (clirun-plugin), the cacheclean module and the backend's clear cache
still remove it together with everything else — that is what "clear the cache" is expected
to mean.