Entry
section tag · entry tag · path
Technical case study · Morae
CMS events · Next.js caching
Extending and debugging an event-driven cache revalidation system across Craft CMS, GraphQL and Next.js.
View Morae project →01 / The model
This production platform combines SSR, on-demand static generation and the persistent Next.js Data Cache. CMS content has no time-based ISR, so meaningful Craft events drive explicit invalidation.
Invalidation does not immediately rebuild every page. The next affected request fetches fresh data and produces an updated response according to that route’s rendering strategy.
02 / Dependencies
A relational CMS mutation can affect more than one route. Craft emits semantic information so Next.js can invalidate fetches and pages associated with meaningful tags and paths.
section tag · entry tag · path
global_<handle>
category-group handle
seomatic
03 / The bug
When an Entry was disabled from Craft’s element index, it could remain represented by stale cached data even though it was no longer enabled in the CMS.
Perhaps element-index actions followed a different lifecycle from an individual save.
Elements::EVENT_AFTER_PERFORM_ACTIONI added temporary instrumentation, queried the action criteria and logged affected sections. It did not execute invalidations and was removed after testing the idea.
04 / Root cause + fix
The existing save handler checked the Entry’s new enabled state. Once the Entry became disabled, that guard returned before triggering the invalidation needed to remove its previous cached representation.
Entry::EVENT_AFTER_SAVE↓check entry->enabled↓disabled → returnEntry::EVENT_AFTER_SAVE↓Invalidate relevant dependenciesAFTER_DELETEAFTER_RESTOREThe smallest correct fix was to remove the enabled-state guard, retaining the Entry lifecycle approach. Delete and restore remain explicit lifecycle paths.
05 / Beyond Entries
Asset → nested block → root Entry → cached page
Asset → Global Set → multiple pages
I extended asset-related invalidation behaviour so changes could reach referencing Entries, root Entries and Global Sets. This is targeted relationship handling alongside the broader semantic tag strategy—not a universal dependency resolver.
06 / Takeaway
Cache invalidation must react to content that has just stopped existing from the frontend’s perspective.
Disabled, deleted and restored content all need cache consideration.
The lifecycle was correct; the enabled-state filter encoded the wrong rule.
The useful unit is often a semantic content dependency, not one current page.
My contribution was to extend the existing revalidation system, extend asset-related invalidation behaviour, investigate and reject the Element Action hypothesis, trace the disabled-entry issue to the enabled-state guard and implement the smallest correct fix.