You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Pages served from shared caches (CDNs, page caches) can contain two kinds of interactive parts:
Parts that can re-render on the client from data. Example: a cart badge in the header. The cached HTML ships neutral markup, the client fetches the visitor's cart from a REST endpoint, and the Interactivity API renders it. These already work with shared caches today.
Parts that can only be rendered on the server, and whose output depends on the visitor. Example: a "recommended for you" section whose contents depend on what's in the visitor's cart, composed of blocks whose render logic only exists in PHP (server-side queries, random ordering, markup that has no client-side counterpart). If the page comes from a shared cache, the region is unpersonalized and nothing on the client can fix it, because the client cannot render it.
This issue proposes exploring a declarative, platform-level mechanism for the second kind:
When the server renders such a region without visitor data (because the response must remain cacheable), the block emits a marker in the region's markup (a directive or similar), carrying the information the client needs to evaluate it (at minimum, the owning store namespace).
On the client, the owning store registers a condition (a hook/predicate) that decides whether a refresh is warranted for this visitor. For example, an e-commerce store would register something like state.cart.items.length > 0.
When the runtime encounters marked regions and the condition approves, it re-fetches the page HTML from the origin (bypassing shared caches) and morphs only the marked regions with the fresh, personalized markup. The refreshed response declares itself non-cacheable and carries no marker, so the cycle terminates by itself.
The key property is the distribution of responsibility: the server declares at render time, which is the only moment when "this region ended up unpersonalized" is knowable, including for future requests it will never see because a cache will answer them. The client contributes only the visitor state it owns. The runtime executes mechanically. Block authors declare; they never construct requests, and there is no public request-level API.
Context
More and more sites serve fully cacheable HTML from CDNs and page caches, and hydrate per-visitor parts on the client. That model covers the first kind of parts well, and leaves the second kind with no supported path.
Client-side navigation to a page containing such a region: the router cannot know the target page's content before fetching it. The only requester-side rule available is "visitor has state, so bypass the cache on every navigation", which destroys the benefit of the shared cache for all pages. With the marker, the navigation fetch stays cache-friendly, and only pages that actually contain marked regions trigger a background refresh, only for visitors whose condition approves.
Multiple regions on one page: N manual self-refreshes versus one refresh patching N regions.
Third-party regions: without a declarative mechanism, every author re-implements the whole pattern by hand (state check, unique URL, navigate), and pressure appears to formalize request-level signals (agreed query params with server-side semantics) to coordinate the scattered call sites. A declarative marker removes the call sites, and with them the need for any public request-level contract.
Prefetching: the router can keep prefetching target URLs cheaply because they hit the shared cache; refreshes happen only on real navigation, driven by the marker and the condition.
Constraints
Zero public API surface for consumers beyond declaring the region. No agreed query params, headers, or shared constants that third-party code must emit or recognize. The transport the runtime uses to reach the origin (a unique URL today, possibly other mechanisms later) is an internal implementation detail and must remain changeable.
Navigations must keep hitting shared caches by default. Any design that degrades into "bypass the cache for every visitor with state" is out.
The marker means "this region, in this response, was rendered without visitor data". It does not mean "this region is of a user-dependent type". Origin-personalized responses carry no marker, so the mechanism is idempotent and cache-configuration-agnostic: it observes each response instead of needing to know how the cache in front is configured.
Refreshed (personalized) responses must declare themselves non-cacheable, preserving the existing invariant that shared caches never store visitor data.
Fail-safe: blocks that don't adopt the mechanism keep exactly today's behavior.
Assumptions / directions to explore
The marker is probably a directive (data-wp-…, name and shape TBD) carrying at least the owning namespace, and possibly extra context needed by the condition.
The condition is registered per namespace in the store and evaluated by the runtime against client state. It should probably support deferral: evaluating on visibility or interaction rather than on navigation, since a region inside a drawer or far below the fold may never need refreshing at all if the visitor never reaches it.
Initial transport hypothesis: re-fetch the full page with a unique URL and extract the marked regions. A future optimization could be server-side fragment rendering through a dedicated endpoint, adopted without changing the declarative surface.
One refresh per page, patching all approved regions, in the background, after first paint, in parallel with the data fetches that client-renderable parts already do.
Server-only output that depends on cookies or session state appears well beyond commerce (personalization, geolocation, memberships, A/B variants), which is why this likely belongs at the platform level rather than inside any one plugin.
Description
Pages served from shared caches (CDNs, page caches) can contain two kinds of interactive parts:
This issue proposes exploring a declarative, platform-level mechanism for the second kind:
state.cart.items.length > 0.The key property is the distribution of responsibility: the server declares at render time, which is the only moment when "this region ended up unpersonalized" is knowable, including for future requests it will never see because a cache will answer them. The client contributes only the visitor state it owns. The runtime executes mechanically. Block authors declare; they never construct requests, and there is no public request-level API.
Context
should_hydratefilter) for serving cacheable store pages, building on the caching strategy described in this comment, and Fix Cross-Sells/Upsells not appearing within the new iAPI Mini-Cart woocommerce/woocommerce#62721 ships a server-only recommendations region that refreshes itself viaactions.navigate()with a cache-busting query param when its container opens. That manual version works because the region and the trigger coincide and the code lives in one place. The general cases don't have that luxury:Constraints
Assumptions / directions to explore
data-wp-…, name and shape TBD) carrying at least the owning namespace, and possibly extra context needed by the condition.