Skip to main content

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 static fragments
  • cached server fragments when cache.html and a positive cache.ttl enable SSR caching

They do not directly clear:

  • RouteService memory 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

  1. The renderer attaches tags to the HTML result.
  2. Static manifests or SSR cache entries store those tags with a generation timestamp.
  3. WordPress records an invalidation timestamp when matching content changes.
  4. Before ReactWP reuses an entry, it compares its generation time with every attached tag's invalidation time.
  5. 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:

TagAttached toPurpose
render:allevery rendered fragmentglobal ReactWP HTML invalidation
template:<name>every rendered fragmenttarget every route using one React template
post:<route-id>a route with an IDconnect a normal post/page route to its WordPress object
menu:allevery rendered fragmentinvalidate output that may contain shared navigation
settings:allevery rendered fragmentinvalidate 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 eventInvalidated tags
post/page/custom post type savedpost:<id>, post-type:<slug>
post/page/custom post type deletedpost:<id>, post-type:<slug>
navigation menu updatedmenu:all
term created, edited, or deletedterm:<id>, taxonomy:<slug>
ACF saves options or optionsettings:all
ReactWP > Cache is usedrender: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:

DependencyRecommended tag
the route's own page or postautomatic post:<id>
every item from one post typepost-type:<slug>
one related post known in advancepost:<id>
one termterm:<id>
any term in one taxonomytaxonomy:<slug>
WordPress navigationautomatic menu:all
shared ACF optionsautomatic settings:all
project-specific external or computed dataa 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

ModeEffect of tags
clientno server HTML entry exists; tags do not clear browser route memory
statica stale fragment is rejected; ReactWP falls back to client rendering until a fresh build or runtime regeneration exists
server, uncachedthe request renders fresh HTML; no SSR entry is available to invalidate
server, cacheda 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:

  1. confirm the fragment contains the expected tag
  2. confirm the WordPress event invalidates the same normalized tag
  3. confirm the fragment generation time predates the invalidation
  4. confirm WP-Cron and the render service are available when runtime static regeneration is expected
  5. 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.