Skip to main content

PHP Runtime

Role

The PHP runtime prepares frontend data, resolves render strategy, selects client/static/server initial HTML, validates cached fragments, connects WordPress to the optional Node renderer, and exposes the public headless contract.

Main Files

The shared runtime lives in the ReactWP mu-plugin:

  • inc/runtime/RouteResolver.php
  • inc/runtime/MenuBuilder.php
  • inc/runtime/Bootstrap.php
  • inc/runtime/ClientCache.php
  • inc/runtime/FieldGroups.php
  • inc/runtime/RenderStrategy.php
  • inc/runtime/InitialRender.php
  • inc/runtime/RenderCache.php
  • inc/runtime/ServerRenderer.php
  • inc/runtime/StaticRegenerator.php
  • inc/runtime/TemplateAssets.php
  • inc/runtime/PublicPayload.php
  • inc/runtime/HeadlessApi.php
  • inc/runtime/RestAccess.php
  • inc/runtime/PreviewToken.php
  • inc/routes/rest.php

PublicPayload, HeadlessApi, and PreviewToken are loaded lazily for headless requests or preview token creation. Normal frontend/admin requests do not need to parse that complete layer.

For the high-level role of the mu-plugin and bundled plugins, see Architecture.

Callable Runtime Surface

The runtime classes currently expose the following public static methods. “Public” here means callable PHP visibility, not that every callback is a long-term project extension point. Prefer the documented rwp facade and hooks for project code; use a class method directly only when its contract below is the behavior you need.

ClassPublic static methodsRole
Bootstrapsystem(), payload($resolved_route = null), json($payload = null)build the integrated system/bootstrap data and JSON-encode it
ClientCacheversion(), bust()read or rotate the global cache generation
MenuBuilderall(), from_location($location, $menu_id = null)normalize every configured menu or one location
RouteResolvercurrent(), from_path(), from_post_id(), from_object(), payload_from_object(), not_found(), is_public_object(), is_public_author(), is_public_term(), normalize_path(), normalize_search(), parse_query()resolve, authorize, normalize, and project route data
RenderStrategyresolve(), normalize(), route_key()resolve bounded render/cache settings and deterministic route identity
InitialRenderresolve(), manifest_entries()choose client/static/server first HTML and read eligible static entries
RenderCacheboot(), invalidate(), is_fresh(), invalidate_post(), invalidate_deleted_post(), invalidate_navigation(), invalidate_term(), invalidate_acf(), invalidate_all()register invalidation listeners, record tag changes, and test fragment freshness
ServerRendererrender(), render_result(), available()request or reuse SSR HTML and report renderer availability
StaticRegeneratorboot(), schedule(), run()queue and process runtime regeneration for invalidated static entries
TemplateAssetsenqueue()enqueue extracted template CSS for accepted initial HTML
PublicPayloadbootstrap(), route(), navigation(), settings(), sanitize_value(), sitemap(), current_user(), response()construct the bounded external contract
PreviewTokencreate(), validate()sign and verify post-scoped preview tokens
RestAccessrequested_route(), is_allowed(), is_namespace(), is_safe_view(), normalize_route()identify and exactly authorize REST paths and safe local views
HeadlessApiboot(), register_routes(), allowed_rest_routes(), allowed_http_origins(), send_cors_headers(), public_permission(), auth_read_permission(), auth_write_permission(), bootstrap(), navigation(), settings(), sitemap(), preview(), current_user(), login(), logout(), allowed_origins(), is_same_origin_request()WordPress hook/REST callbacks for the external API

HeadlessApi callback methods are exposed so WordPress can call them; projects normally configure them with their documented filters instead of calling the callbacks themselves. RenderCache event methods are likewise hook callbacks. Directly calling a resolver or payload projector does not bypass the visibility, size, origin, nonce, or permission checks owned by the endpoint layer.

RouteResolver

RouteResolver builds the normalized route payload used by the frontend.

Its job is to describe:

  • the current route
  • the template name
  • the route data
  • SEO data
  • whether the route is a 404

It also builds the optional head payload used during client-side navigation.

When a route is resolved through the REST endpoint, ReactWP applies the rwp_wp_head filter and stores the resulting tags in route.head. The frontend can then re-sync those tags after a client-side navigation.

MenuBuilder normalizes WordPress menus into a frontend-friendly shape.

Menu locations are not hardcoded in the theme. They are registered from the ReactWP site settings, which keeps the default behavior configurable from the admin.

Bootstrap

Bootstrap prepares the first-load payload injected into the page.

That payload contains:

  • site
  • theme
  • system
  • assets
  • navigation
  • route
  • currentUser
  • seoDefaults

This is the same general contract that the frontend expects during client-side navigation.

The integrated bootstrap also contains a system.cacheVersion and endpoint URLs for the public headless surface.

ClientCache

ClientCache stores the global ReactWP cache generation in WordPress. Despite its historical class name, that generation also invalidates static fragments and cached SSR HTML in both public and private scopes.

It provides:

  • ClientCache::version()
  • ClientCache::bust()
  • rwp::client_cache_version()
  • rwp::bust_client_cache()

The generation participates in frontend Cache Storage names and initial asset version values. ClientCache::bust() fires rwp_client_cache_busted after updating the option.

RenderStrategy

RenderStrategy resolves and normalizes the route's initial-render contract:

  • client, static, or server mode
  • HTML cache enablement, scope, and TTL
  • browser payload and media cache flags
  • dependency tags

It combines JavaScript registry metadata, PHP template defaults, route values, ACF route overrides, and final project filters. Invalid modes return to client; invalid scopes return to the mode-appropriate default; TTL values cannot be negative; invalid cache tags are discarded.

The route key combines normalized language, pathname, and search string. Hashes are browser-local scroll state and are not part of static or SSR identity.

See Configuration Reference for exact defaults and precedence.

InitialRender

InitialRender runs only for eligible frontend GET requests. It does not pre-render admin, Ajax, REST, feed, trackback, or non-GET requests.

Its decision is intentionally fail-safe:

  1. client returns an empty #app shell for normal browser rendering.
  2. static looks for a fresh runtime or build manifest entry.
  3. server asks ServerRenderer for cached or request-time HTML.
  4. any unavailable, invalid, oversized, stale, or unreadable result falls back to client.

Static fragments must stay inside their manifest directory, must match the active ReactWP cache generation, and are limited to 5 MiB by default. rwp_static_render_max_html_bytes changes that read limit.

RenderCache

RenderCache stores tag invalidation timestamps in WordPress and checks fragment freshness before reuse. It invalidates tags automatically for post saves/deletes, menu updates, term changes, ACF options saves, and the global ReactWP cache action.

It does not run a WordPress query for a tag or delete every matching file synchronously. A fragment is stale when one of its dependency tags was invalidated after the fragment was generated.

See Cache Tags for the complete lifecycle, automatic tags, custom dependencies, and debugging workflow.

ServerRenderer

ServerRenderer connects WordPress to the optional Node /render service. It owns:

  • endpoint and secret handling
  • loopback-only defaults and remote endpoint opt-in
  • request timeout and response-size limits
  • a short failure circuit breaker
  • public/private SSR cache key construction
  • per-user private identities
  • cache freshness checks through RenderCache
  • clean client fallback when SSR is unavailable

Public SSR entries are shared only for anonymous requests. Logged-in users bypass public SSR cache. Private entries use the WordPress user ID by default and require a project-provided identity before anonymous private caching is allowed.

StaticRegenerator

StaticRegenerator listens to rwp_render_cache_invalidated. When the renderer is available, it intersects invalidated tags with static manifest entries and queues matching routes in WordPress.

WP-Cron processes 10 routes per event by default. A failed route is attempted at most three times. Fresh runtime fragments are written under protected WordPress uploads and override build-generated entries for the same route.

Without a renderer, invalidated static HTML is rejected and the route uses client rendering until the next static build.

TemplateAssets

TemplateAssets reads templates.json and template-assets.json before wp_head(). For a valid static or server initial response, it enqueues the extracted CSS associated with that template's assetKey so the pre-rendered HTML is styled before hydration.

It does not enqueue route CSS through PHP for a client-only initial response; Webpack's lazy template chunk loads that CSS in the browser. Manifest paths containing traversal, missing files, and non-CSS entries are ignored.

PublicPayload and HeadlessApi

PublicPayload converts internal route/bootstrap data into the stable external response shape. It normalizes public object references and attachment data instead of exposing arbitrary WordPress objects directly.

HeadlessApi registers public content, preview, and authentication endpoints. It also owns exact-origin CORS checks, login throttling, nonce requirements, and no-store responses for sensitive endpoints.

RestAccess normalizes REST route paths and applies exact allowlist matching for public and authenticated cross-origin access. Passing that global gate only makes a route reachable; the route's own permission_callback must still authorize its data or action.

PreviewToken

PreviewToken creates and validates signed, expiring tokens scoped to one post ID. Project code normally calls rwp::preview_token() instead of instantiating it directly.

FieldGroups

FieldGroups.php registers the runtime-side ACF fields that control:

  • media_groups
  • react_template
  • react_render_mode
  • react_render_cache_scope
  • react_render_cache_ttl

Those field groups are enabled for:

  • posts
  • pages
  • users
  • taxonomies
  • any extra post types selected in Site settings

REST Route

inc/routes/rest.php exposes the route endpoint used for client-side navigation:

  • reactwp/v1/route

That route returns the same normalized route shape used by the first bootstrap payload.

ReactWP also restricts REST access by default and lets public routes through rwp_allowed_rest_routes.

By default:

  • authenticated admins keep normal REST API access when the request includes valid WordPress REST authentication, such as the REST nonce
  • non-admin users, guests, and unauthenticated external requests are blocked
  • any route explicitly allowed through rwp_allowed_rest_routes stays accessible

ReactWP exposes a few runtime filters that are important when you need to customize route payloads or head tags:

  • rwp_route_payload
  • rwp_wp_head

If you need to add or change meta tags, canonical tags, Open Graph tags, or other custom head markup that must also work after React navigation, use rwp_wp_head.

For the full rwp_wp_head behavior and context payload, see Head and SEO.

For the full payload structure and the public starter filters, see: