Performance
Divine’s runtime performance behavior is owned by the theme, in a single file: divine.json at the theme root. The file declares a performance object that the runtime validates and resolves, and the same configuration drives plugin-managed themes and exported standalone themes alike. Defaults are conservative on purpose, so nothing is optimized away until the theme opts in, and because the file lives in the theme, performance changes travel through worktrees, reviews, deploys, and exports like any other theme file. This page is the reference for the performance object, its profiles and sections, and the order in which Divine merges everything into the configuration that actually runs.
divine.json
divine.json is the theme-level source of truth for runtime performance. The runtime reads the performance key from it, validates the result strictly, and ignores an invalid configuration with errors recorded rather than applying it partially. Unknown keys, wrong value types, and out-of-range values are all rejected. Start from a profile and override individual sections from there.
{
"performance": {
"profile": "speed",
"media": { "rootMargin": "600px" },
"staticPrerender": { "enabled": true, "ttl": 86400 }
}
}
Because the configuration is theme-owned, the same file is exported with the standalone theme and continues to govern performance on a site that has no Divine plugin installed.
Profiles
The profile key selects a baseline that individual sections then override. There are three profiles. compat is the default and leaves WordPress untouched, which makes it safe to adopt Divine without changing front-end behavior. speed turns on the common optimizations, and strict extends speed with the structured data policy.
| Profile | Behavior |
|---|---|
compat |
The default. Leaves WordPress behavior untouched; every optimization stays off. |
speed |
Enables the WordPress cleanup set, Blockstudio discovery and render caches, intent link preloading, and lazy media. |
strict |
Everything in speed, plus the structured data policy with postmeta queries disabled. |
Two cross-field rules are enforced during validation, so the resolver can reject a contradictory theme configuration rather than apply half of it. The strict profile requires data.policy to be structured, and a structured policy cannot re-enable data.postmetaQueries.
Configuration sections
Each section is an object with explicitly typed keys; booleans are real booleans, and enumerated values are validated against their allowed set. The defaults below are the compat baseline that ships when a key is absent.
| Section | Keys | Defaults | What it controls |
|---|---|---|---|
wordpress |
headNoise, embeds, xmlrpc, editor, frontendAssets, media, heartbeat |
all false |
WordPress cleanup toggles: removing default head output, embed machinery, XML-RPC exposure, editor and frontend asset overhead, media extras, and heartbeat throttling. |
blockstudio |
uiApps, discoveryCache, renderCache |
uiApps true, caches false |
Whether the bundled Blockstudio loads its UI apps and whether block discovery and render caches are enabled. |
data |
policy, allowedSources, postmetaQueries |
compat, ["pages","cpts"], true |
The theme data policy: compat or structured, the allowed WordPress sources (pages, cpts), and whether postmeta-driven queries are permitted. |
preload |
links |
off |
Link preloading: off or intent, which warms same-origin links on hover, focus, or touch via a small deferred script that respects data-saver connections. |
media |
lazy, skeleton, metadata, rootMargin |
false, false, false, 300px |
The frontend media loader: lazy loading, skeleton placeholders, metadata-driven sizing, and the IntersectionObserver root margin as a pixel or percentage length. |
measurement |
enabled, queryMonitor, headers, timings |
all false |
Request measurement: master switch, slow-query capture, response headers, and per-request timing output. |
staticPrerender |
enabled, ttl, invalidate |
false, 86400, signature |
The anonymous HTML response cache: master switch, lifetime in seconds, and the invalidation mode, currently signature. |
WordPress cleanup
The wordpress section is a set of independent toggles over the noisiest parts of a default WordPress page. Each one defaults to false so compat ships an unmodified site; the speed and strict profiles flip the whole set to true. Turn an individual toggle back off in the theme when one piece of WordPress output is still needed.
Blockstudio
Divine bundles a scoped Blockstudio build, and the blockstudio section controls how it runs at request time. uiApps defaults to true for the authoring experience and is turned off by the optimizing profiles, while discoveryCache and renderCache default to false and are enabled by speed and strict so block discovery and render output are cached between requests.
Data policy
The data section selects how Divine resolves theme content. The compat policy preserves normal WordPress query behavior, and the structured policy restricts content to declared sources. allowedSources lists which WordPress sources participate, and postmetaQueries decides whether postmeta-driven queries run. The structured policy is what the strict profile requires, and it is the one configuration the validator will not let you partially defeat: a structured policy with postmetaQueries set back to true is rejected.
Preload
The preload section has a single key, links. With intent, Divine enqueues a small deferred script that warms same-origin links when the user signals intent through hover, focus, or touch, and it backs off on data-saver connections. The off default enqueues nothing.
How the effective config is resolved
Several layers merge into one effective configuration, each able to refine the previous one. Divine resolves them in a fixed order so the source of any value is predictable, and validation guards every step: a layer that fails validation is dropped back to a safe value and its errors are recorded, rather than corrupting the merge.
| Step | Source | Role |
|---|---|---|
| 1 | Built-in defaults | The compat baseline every install starts from. |
| 2 | divine/performance/default_config filter |
Lets platform code restate the defaults before anything merges. |
| 3 | DIVINE_PERFORMANCE_PROFILE constant |
Pins a profile above the defaults when defined. |
| 4 | Theme profile | The profile named in divine.json expands into its baseline. |
| 5 | Theme config | The theme’s explicit performance values, read through the raw_theme_config and theme_config filters with validation between them, override the profile baseline. |
| 6 | Policy overlay | A host policy file named by DIVINE_PERFORMANCE_POLICY merges after the theme, including its own profile when it names one, so platform operators can enforce site-wide decisions. |
| 7 | divine/performance/effective_config filter |
Filters the merged result before the final validation and per-theme cache. |
The practical reading order is simple: defaults, then the profile, then the theme’s own values, then host policy, then filters, with validation guarding every step. All five performance filters are listed with their exact arguments in the generated hook reference. The result is validated once more and cached per theme root, so the merge runs once per theme rather than once per request.
The performance runtime is also lazy in a second sense: each capability only attaches its hooks when its section is enabled in the resolved configuration. A compat theme registers nothing on render, so the cost of Divine’s performance layer on an unconfigured site is effectively zero, and an exported standalone theme behaves the same way.
Static prerendering
When staticPrerender.enabled is true, Divine captures fully rendered HTML responses for anonymous GET requests and serves them from disk on subsequent hits, marked with an X-Divine-Static-Prerender: HIT header. The cache is deliberately narrow: requests with query strings, logged-in users, admin, REST, AJAX, cron, login, feeds, and search are always bypassed, and only complete HTML documents are stored.
Cached files live under wp-content/uploads/divine-static-prerender/ unless the DIVINE_STATIC_PRERENDER_CACHE_DIR constant points elsewhere. Entries expire after ttl seconds, and the cache key embeds a signature over the effective performance config and the theme’s key files, divine.json, style.css, functions.php, blockstudio.json, and the pages/, blocks/, templates/, and assets/ directories, so changing the theme invalidates stale HTML structurally. The cache is also purged outright when posts are saved or deleted, the theme switches, or a worktree deploys or is destroyed.
Media loading
With media.lazy enabled, Divine enqueues its media script and stylesheet, which lazy-load images with an IntersectionObserver using the configured rootMargin, optionally render skeleton placeholders, and use stored metadata for stable sizing. Themes render compatible markup with the divine_media_image() template helper, which accepts the image arguments and produces the markup the loader expects, falling back to eager markup when lazy loading is off.
Stable sizing comes from <theme-root>/assets/media.json. Regenerate it after adding or replacing theme images with the runtime builder:
( new \Divine\Runtime\Media\MediaMetadataBuilder() )->write( get_stylesheet_directory(), true );
The builder records raster dimensions for theme assets under assets/ and, when requested, attachment metadata from the WordPress media library. SVG files are ignored for raster sizing instead of treated as errors. MediaMetadata::reset() clears the in-request reader cache after a rebuild, and write() calls it automatically.
Measurement
Measurement is for local profiling and stays off unless measurement.enabled is true. When enabled, Divine records a per-request snapshot, profile, config hash, request URI, elapsed time, peak memory, and query count, in a short-lived transient for inspection, and measurement.queryMonitor adds the slow queries observed during the request to that snapshot. With measurement.headers enabled, responses carry X-Divine-Performance-Profile and X-Divine-Performance-Config headers, plus X-Divine-Performance-Time when timings is also on. The divine/performance/measurement_enabled action fires once measurement is active, so profiling integrations can attach to exactly the requests being measured.