Cache Tags
Purpose
Cache tags describe which WordPress data a generated HTML fragment depends on. They let ReactWP invalidate only the static and cached SSR entries affected by a content change.
registerTemplate('Home', {
loader: () => import('../../templates/Home'),
render: 'static',
cache: {
tags: ['post-type:project']
}
});
This configuration means that the generated Home HTML depends on the project post type. Saving or deleting any project invalidates the fragment.
Tags are dependency labels. They are not cache keys, storage names, TTL values, WordPress queries, or selectors. ReactWP does not inspect a template to discover its data dependencies; project code must declare dependencies that are not part of the current route.
Where Tags Apply
Tags apply to server-side HTML produced by the universal renderer:
- generated
staticfragments - cached
serverfragments whencache.htmland a positivecache.ttlenable SSR caching
They do not directly clear:
RouteServicememory inside an already-open browser tab- browser HTTP cache
- JSON or media Cache Storage generations
- a host or CDN cache
- uncached request-time SSR output
The global ReactWP > Cache action coordinates several ReactWP layers at once, but a targeted cache.tags invalidation is specifically an HTML-fragment dependency mechanism.
Lifecycle
- The renderer attaches tags to the HTML result.
- Static manifests or SSR cache entries store those tags with a generation timestamp.
- WordPress records an invalidation timestamp when matching content changes.
- Before ReactWP reuses an entry, it compares its generation time with every attached tag's invalidation time.
- If any matching invalidation is newer, the complete entry is stale.
Tags use OR semantics. An entry tagged with post-type:project and settings:all becomes stale when either tag is invalidated.
ReactWP records invalidation timestamps instead of scanning and deleting every cache entry synchronously. This keeps a WordPress save request predictable while still preventing stale fragments from being served.
Tags Attached Automatically
Every static or server render receives these baseline tags:
| Tag | Attached to | Purpose |
|---|---|---|
render:all | every rendered fragment | global ReactWP HTML invalidation |
template:<name> | every rendered fragment | target every route using one React template |
post:<route-id> | a route with an ID | connect a normal post/page route to its WordPress object |
menu:all | every rendered fragment | invalidate output that may contain shared navigation |
settings:all | every rendered fragment | invalidate output that may contain shared ACF options |
post:<route-id> is most useful for normal post and page routes. For term, user, virtual, or project-defined routes, declare an explicit domain tag instead of assuming that the route ID identifies a post.
Because menu:all and settings:all are baseline dependencies, updating a menu or ACF options page can invalidate many fragments. This is intentionally conservative: navigation and project settings are commonly shared throughout the shell.
Automatic WordPress Invalidations
ReactWP emits these tags when WordPress changes:
| WordPress event | Invalidated tags |
|---|---|
| post/page/custom post type saved | post:<id>, post-type:<slug> |
| post/page/custom post type deleted | post:<id>, post-type:<slug> |
| navigation menu updated | menu:all |
| term created, edited, or deleted | term:<id>, taxonomy:<slug> |
ACF saves options or option | settings:all |
| ReactWP > Cache is used | render:all |
Autosaves and post revisions do not trigger post invalidation.
An automatically emitted tag only affects fragments that carry the same tag. For example, ReactWP emits post-type:project when a project changes, but a Home fragment must declare post-type:project before that event can invalidate it.
template:<name> is attached automatically, but ReactWP does not assume that changing a JavaScript file happened through WordPress. Invalidate it explicitly or use the global cache action after deploying template code.
Choosing Dependencies
Use the narrowest tag that describes the complete dependency:
| Dependency | Recommended tag |
|---|---|
| the route's own page or post | automatic post:<id> |
| every item from one post type | post-type:<slug> |
| one related post known in advance | post:<id> |
| one term | term:<id> |
| any term in one taxonomy | taxonomy:<slug> |
| WordPress navigation | automatic menu:all |
| shared ACF options | automatic settings:all |
| project-specific external or computed data | a custom namespace:value tag |
A tag that is too broad causes extra regeneration. A tag that is too narrow can leave stale HTML. If a template lists arbitrary projects, post-type:project is safer than attaching only the projects present during the last render unless the project also invalidates additions and deletions correctly.
Declaring Tags in JavaScript
Declare template-wide dependencies in the registry:
registerTemplate('Home', {
loader: () => import('../../templates/Home'),
render: 'static',
cache: {
tags: [
'post-type:project',
'taxonomy:project-type'
]
}
});
The same configuration works for render: 'server':
registerTemplate('ProjectDashboard', {
loader: () => import('../../templates/ProjectDashboard'),
render: 'server',
cache: {
html: true,
scope: 'private',
ttl: 60,
tags: ['post-type:project']
}
});
Tags matter only while an HTML entry is stored. A server route with html: false or ttl: 0 renders on demand without reusing an SSR fragment.
Declaring Tags from PHP
Use rwp_render_templates for a PHP-owned template default:
add_filter('rwp_render_templates', function($templates){
$templates['Home'] = [
'mode' => 'static',
'cache' => [
'tags' => ['post-type:project'],
],
];
return $templates;
});
Use rwp_render_config when a dependency is route-specific or must be calculated from WordPress data:
add_filter('rwp_render_config', function($config, $route, $object){
if(($route['template'] ?? '') !== 'Landing'){
return $config;
}
$featured_id = (int)get_field('featured_project', 'option');
if($featured_id > 0){
$config['cache']['tags'][] = 'post:' . $featured_id;
}
return $config;
}, 10, 3);
The final route payload carries the normalized render configuration to static generation and SSR, so dynamically added tags are stored with the resulting fragment.
Creating a Custom Tag
Custom tags are useful for project data that has no built-in WordPress invalidation event.
First, attach the dependency:
cache: {
tags: ['homepage:featured-projects']
}
Then invalidate the exact same tag when the source data changes:
add_action('save_post_project', function($post_id){
if(wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)){
return;
}
rwp::invalidate_render_cache('homepage:featured-projects');
}, 30);
Multiple tags can be invalidated together:
rwp::invalidate_render_cache([
'homepage:featured-projects',
'catalog:featured',
]);
Calling rwp::invalidate_render_cache() with no argument invalidates render:all.
Tag Syntax
Tags use this shape:
namespace:value
ReactWP trims and lowercases tags. A valid tag must match:
^[a-z0-9_-]+:[a-z0-9_.-]+$
Valid examples:
post:42
post-type:project
taxonomy:project-type
homepage:featured-projects
inventory:montreal_warehouse
feed:external.v2
Invalid examples:
projects
post type:project
homepage/featured
homepage:
Invalid values are discarded during normalization. Prefer stable slugs and IDs. Do not put secrets, user emails, session tokens, or unbounded arbitrary input in a tag.
Listening for Invalidation
ReactWP fires rwp_render_cache_invalidated after normalized invalidation timestamps are stored:
add_action('rwp_render_cache_invalidated', function($tags, $timestamp){
my_project_log_cache_invalidation($tags, $timestamp);
}, 10, 2);
The optional static regenerator listens to this action and queues manifest entries whose stored tags intersect with the invalidated tags. Project code can also use it for logging or provider-specific revalidation.
Do not call rwp::invalidate_render_cache() for the same tags from inside this action; that creates a recursive invalidation loop.
Behavior by Render Mode
| Mode | Effect of tags |
|---|---|
client | no server HTML entry exists; tags do not clear browser route memory |
static | a stale fragment is rejected; ReactWP falls back to client rendering until a fresh build or runtime regeneration exists |
server, uncached | the request renders fresh HTML; no SSR entry is available to invalidate |
server, cached | a stale SSR entry becomes a cache miss and the renderer creates fresh HTML for the request |
When the SSR service is available, static entries affected by an invalidation are queued through WP-Cron. The default batch size is 10 and each failed entry is attempted at most three times.
Inspecting Tags
Build-generated static tags are visible in:
wp-content/themes/<theme>/assets/render/static/manifest.json
Runtime-regenerated tags are visible in:
wp-content/uploads/reactwp/render/static/manifest.json
Each manifest entry includes its route key, generation time, cache generation, file, and tags. Runtime entries override build entries for the same route.
If a page remains stale:
- confirm the fragment contains the expected tag
- confirm the WordPress event invalidates the same normalized tag
- confirm the fragment generation time predates the invalidation
- confirm WP-Cron and the render service are available when runtime static regeneration is expected
- remember that an already-open tab can still hold a separate in-memory route payload
Continue with Cache and Revalidation for every cache layer and Client, Static, and Server Rendering for deployment behavior.