Frequently asked questions
Why does my entire site cache expire when one page has a future starttime?
Because the default scoping strategy is global.
Its transition lookup covers every monitored table site-wide and ignores the page id, so
every page cache entry written in that second gets the same shortened lifetime.
Switching to per-page narrows it — but only for content elements:
$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['nr_temporal_cache']['scoping']['strategy'] = 'per-page';
A transition in the pages table still shortens every page's lifetime, in every strategy,
because a page entering or leaving the tree changes menus everywhere.
Warning
Switching to per-content does not help here.
That strategy narrows flush tags, not lifetimes, and flush tags are only read by
scheduler and hybrid timing.
With the default dynamic timing it behaves exactly like global.
Can I disable this for specific page trees?
Not through configuration. The settings are global to the installation; there is no page-tree, doktype or content-type filter.
The workaround is a second listener on the same event, ordered after this extension's, that overrides the lifetime for the pages it should not apply to:
namespace MyVendor\MyExtension\EventListener;
use TYPO3\CMS\Frontend\Event\ModifyCacheLifetimeForPageEvent;
final class ConditionalTemporalCache
{
/**
* @param int[] $excludedPageIds pages that keep the long lifetime
*/
public function __construct(private readonly array $excludedPageIds = [])
{
}
public function __invoke(ModifyCacheLifetimeForPageEvent $event): void
{
if (\in_array($event->getPageId(), $this->excludedPageIds, true)) {
$event->setCacheLifetime(86400);
}
}
}
services:
MyVendor\MyExtension\EventListener\ConditionalTemporalCache:
tags:
- name: event.listener
identifier: 'my-extension/conditional-temporal-cache'
event: TYPO3\CMS\Frontend\Event\ModifyCacheLifetimeForPageEvent
after: 'temporal-cache/modify-cache-lifetime'
Will this work with a CDN or Varnish?
Yes, with one caveat.
A CDN honors the Cache-Control window it is given, so a shortened page cache lifetime
propagates to the edge.
With global scoping every entry carries the same expiry, so the edge misses arrive
together and reach the origin as one wave.
The mitigations are the usual ones — serve stale while revalidating, rate-limit the origin,
warm the cache ahead of a known transition — plus the extension-side option of moving to
scheduler timing, which stops shortening lifetimes altogether.
See Synchronized expiry with global scoping.
Does this affect backend performance?
The cache lifetime listener runs on frontend page cache writes only, so ordinary backend editing is untouched.
Three parts of the extension do run in the backend, on demand: the backend module, the Reports module status provider, and the console commands. The backend module and the harmonization analysis load temporal records to build their figures, so they get slower as the amount of temporal content grows.
What if I do not use temporal content at all?
Every lookup returns null and the lifetime falls back to
advanced.default_max_lifetime (default 86400), so behavior is unchanged.
The queries still run on every page cache write with dynamic timing.
There is no benefit in that case — uninstall it.
How do I see what the extension is doing?
Turn on advanced.debug_logging.
The listener then logs, for every page cache write it modifies: the lifetime it set, the
uncapped value, the cap and where the cap came from, and the names of both active
strategies.
$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['nr_temporal_cache']['advanced']['debug_logging'] = true;
The same flag makes SchedulerTimingStrategy log each flush with the tags it flushed, and
makes the scheduler task log its run window.
To see the data rather than the decisions:
vendor/bin/typo3 temporalcache:analyze # counts and statistics
vendor/bin/typo3 temporalcache:list # every temporal record and its next transition
vendor/bin/typo3 temporalcache:verify # indexes and configuration
Can I combine this with cache warming?
Yes, and it is worth doing with global scoping, where all entries expire at the same
known moment.
temporalcache:list reports the next transition per record, so a warming run can be
scheduled shortly after it.
The extension ships no warming itself and integrates with no particular warming extension; anything that requests pages after the transition works.
What happens under load when the cache expires?
With global scoping, every page cache entry expires at the same second, so every
subsequent request is a miss until the entries are rebuilt.
Under load that arrives at the origin as a single burst.
Four levers, in rough order of effectiveness:
schedulertiming — nothing expires by time.per-pagescoping — content transitions stagger; page transitions do not.- Stale-while-revalidate at the edge, so the burst is absorbed.
- Cache warming timed to the known transition.
How does this work with workspaces?
With dynamic timing, correctly and without configuration.
The strategies read the workspace id from the Context API and pass it into every query; a
live request and a workspace preview therefore resolve different transitions and get
different lifetimes.
Warning
With scheduler or hybrid timing this does not hold.
The scheduler task looks for transitions in the live workspace and the default language
only, so transitions on workspace versions and on translated records are not processed.
Can I combine this with my own cache tags?
Yes.
The extension sets the page cache lifetime and, under scheduler timing, flushes
pages or pageId_* tags.
It does not interfere with tags your own code adds or flushes.
If your listener also sets a lifetime, order it after
temporal-cache/modify-cache-lifetime and combine the two values with min() rather
than overwriting.
What does harmonization cost?
Nothing at runtime — it changes no code path.
temporalcache:harmonize rewrites the stored starttime/endtime values once, and
from then on the same transition lookups simply find fewer distinct moments.
The real cost is editorial: publication times move to the nearest slot within the
configured tolerance.
Preview with --dry-run before running it, because the command writes by default.
Does the query cost multiply with the number of languages?
No.
One frontend request carries one language, and the language id from the Context API goes
into the query as a single sys_language_uid condition.
A ten-language site runs the same number of queries per cache write as a single-language
site — each language just maintains its own cache entries and its own transitions.
The number of queries grows with the number of monitored tables, at two per table.
Next steps
- Optimization strategies — what each setting changes
- Limitations — what none of them fixes
- Decision guide — choosing a configuration
- Architecture — the implementation behind the answers