Architecture
The Platform
ReactWP is a project platform made of four cooperating systems:
- the source-to-WordPress build pipeline
- the shared mu-plugin runtime
- opinionated WordPress plugins
- the React theme runtime
The integrated theme and the external headless API share the same WordPress route resolver, but expose different payload contracts for their consumers.
Build System
configs/ compiles authored files from src/ into the runnable dist/ installation.
It owns:
- theme, plugin, and mu-plugin bundles
- development, watch, and production modes
- React/JSX compilation
- global SCSS and template-owned CSS chunks
- asset copying and production image optimization
- entrypoint manifests and stale output cleanup
- JavaScript code splitting
- client, SSG, and optional SSR render bundles
- static fragment generation and template CSS manifests
- production Brotli/gzip files and bundle budgets
The build is not a runtime dependency. WordPress serves the generated output after deployment.
Mu-Plugin Runtime
The ReactWP mu-plugin is the application backend. It loads before normal plugins and provides:
- normalized route resolution
- the integrated bootstrap payload
- normalized WordPress navigation
- route, headless, preview, and authentication endpoints
- public payload sanitization
- the REST allowlist gate
- ACF runtime field groups
- Site settings and Theme settings
- global ReactWP cache generation and invalidation
- first-boot defaults
- PHP helper APIs
Important runtime classes:
BootstrapRouteResolverMenuBuilderClientCacheRenderStrategyRenderCacheInitialRenderServerRendererStaticRegeneratorTemplateAssetsPublicPayloadHeadlessApiPreviewToken
The larger headless classes are loaded only for ReactWP headless requests or when preview token support is called. This avoids parsing the full headless layer on unrelated admin screens.
Bundled Plugins
The exact hooks and limits of all six generated normal plugins are listed in Bundled WordPress Plugins. The summaries below describe their architectural ownership.
reactwp-frontend
Owns a cleaner public WordPress surface. It hides the admin bar and removes selected block/classic styles, speculation rules, auto sizes, and extra head behavior that conflicts with the controlled theme runtime.
reactwp-images
Disables intermediate image sizes and sets image quality to 100. This is intentionally opinionated; projects that rely on WordPress responsive derivatives should change or disable that policy.
reactwp-backend
Disables Gutenberg, simplifies dashboard/admin menus, and creates the ReactWP admin-bar group with links to settings, themes, plugins, cache, ACF, import, and export according to the current user's capabilities.
reactwp-seo
Builds titles, descriptions, Open Graph values, favicons, and robots behavior. It renders tags on the PHP request and provides the same tag set in route.head for client navigation.
reactwp-accept-svg
Allows SVG uploads after filename sanitation and structured XML sanitization through enshrined/svg-sanitize. The plugin removes executable markup, strips external href references, and stores the sanitized document instead of trusting the original upload.
reactwp-acf-local-json
At runtime, points ACF Local JSON at the built active theme's datas/acf/ directory. Author those files in src/themes/<theme>/template/datas/acf/; the theme build copies them to dist/wp-content/themes/<theme>/datas/acf/.
React Theme Runtime
The theme supplies both the PHP shell and the browser application.
The PHP side:
- prints stable mount and scroll nodes
- injects the bootstrap JSON
- selects client, static, or server initial rendering
- injects a valid pre-rendered fragment when available
- enqueues extracted CSS for a pre-rendered template
- enqueues manifest assets
- prints small critical shell CSS
- defines project filters and media maps
The React side:
- reads and normalizes the bootstrap
- resolves lazy route templates and hydrates pre-rendered routes
- integrates browser history through React Router
- fetches WordPress route payloads
- preloads critical fonts/media and progressive deferred groups
- coordinates first load and route transitions
- updates
<head>after navigation - manages smooth scrolling and scroll locks
- mounts persistent shell components
Integrated Request Lifecycle
Direct Request
browser URL
-> WordPress query
-> RouteResolver::current()
-> Bootstrap::payload()
-> InitialRender::resolve()
-> client fallback, static fragment, or SSR service
-> PHP shell + #reactwp-bootstrap + #app
-> React mount or hydration
-> lazy template + critical assets
-> route reveal
Client Navigation
AppLink / browser history
-> /reactwp/v1/route?view=...
-> normalized route
-> template and critical asset preparation
-> leave animation
-> route mount + head sync + scroll target
-> enter animation
-> progressive deferred media
pathname + search identifies a route. A hash identifies a scroll target inside that route.
External Headless Lifecycle
An external frontend calls the public endpoints under /wp-json/reactwp/v1/. PublicPayload transforms internal WordPress values into a smaller stable response and sanitizes attachments and object references where possible.
External applications should use that public contract instead of parsing the integrated theme's inline bootstrap shape. The two modes share data sources, not every implementation detail.
State Boundaries
ReactWP deliberately has several state layers:
- WordPress/database state
- PHP request-local route and settings state
- browser route memory for the current tab
- browser Cache Storage for JSON/media generations
- HTTP asset cache controlled by URLs and headers
Understanding which layer owns a stale value is essential when debugging. See Cache and Invalidation.
For the complete render lifecycle and deployment choices, see Hybrid Rendering.
Extension Boundaries
Prefer:
- theme
template/functions.phpfor project PHP filters and WordPress hooks js/inc/config/for loader, transition, and template registrationjs/templates/andjs/components/for renderingscss/for styling- a project plugin for reusable business behavior or custom REST endpoints
Change the shared mu-plugin only when the platform contract itself needs to evolve.