superpowers-sage:wp-performance
WordPress performance optimization: performance, slow query, N+1 query, Query Monitor, autoload audit, autoload options, object cache, Redis, wp_cache, transient, cache invalidation, Core Web Vitals,
Files1
View on GitHubSKILL.mdskills/wp-performance/SKILL.md
GitHubWordPress performance (wrapper)
Full procedure: third-party/wp-performance/SKILL.upstream.md (official WordPress skill, GPL-2.0-or-later, see third-party/UPSTREAM.md). Read it first; this file only adds what is specific to Roots projects run through a container runner. Paths starting with third-party/ are relative to the kivo-wise plugin root, two levels above this skill's folder (<skill dir>/../../third-party/...), not inside the skill folder. In your answer, point the user to that full procedure by its resolved absolute path, and use the same resolution for any other third-party/ path you name.
When to use
- A page, REST route or admin screen is slow, or the query count looks high.
- You need a backend-only baseline before changing anything: autoloaded options size, object-cache drop-in,
wp doctor,wp profile. - The Redis object cache is suspected (stale data, low hit rate, flush behaviour).
- You are about to add a transient, a cache layer or an index and want evidence first.
Inputs required
- Active profile and runner from the session context (
{{runner.wp}},{{runner.acorn}},{{runner.cache}}). Lando default:lando wp,lando wp acorn, the project's cache-clear tooling command. - The URL to measure and the WordPress path as seen inside the container (not the host path).
- Whether the object cache is enabled in this environment (a project may disable Redis through an environment constant).
Procedure
Commands: write every command with the runner value from the session context filled in (for example lando wp acorn optimize:clear), never a bare wp/composer/php/npm, never the placeholder, never "prefix with lando if you use it". Commands that run in CI or a deploy script go in a separate block labelled with where they run.
Measure before touching code. Never optimize blind. Record a baseline twice for the same URL: cold (right after
{{runner.cache}}and, locally,{{runner.wp}} cache flush) and warm (the next request), each with time, query count and cache state, and keep both for the comparison. Point the user to the official procedure file (resolved path ofthird-party/wp-performance/SKILL.upstream.md) for the full checklist.Run the deterministic report through the runner. The official script reads the runner from
WP_RUNNER(a local patch, listed inthird-party/UPSTREAM.md):WP_RUNNER="{{runner.wp}}" node third-party/wp-performance/scripts/perf_inspect.mjs --path=<container path> [--url=<url>]On Lando
{{runner.wp}}islando wp, so the value isWP_RUNNER="lando wp". Without it the script calls a barewpon the host and fails.Check the object cache before suggesting any cache code. WordPress loads a
wp-content/object-cache.phpdrop-in when present, andwp_using_ext_object_cache()reports it; with no drop-in the cache lives only for one request. Confirm with{{runner.wp}} cache typeand, for a Redis plugin, its status subcommand. Name the native API that a fix would use (wp_cache_get()/wp_cache_set()with a group,get_transient()/set_transient()); a persistent drop-in makes those calls survive across requests without new code. If the project sets a key salt or prefix constant, a flush from one environment must not touch another.Profile with WP-CLI.
{{runner.wp}} doctor check --all, then{{runner.wp}} profile stage --allandprofile hook. Pass--urlso the right site is loaded (see the profile table for multisite and language-per-domain setups).Pick the fix from the official references (
third-party/wp-performance/references/:database.md,object-cache.md,autoload-options.md,http-api.md,cron.md,server-timing.md). Native first: usewp_cache_get/setwith a group,get_transient,WP_Queryarguments (no_found_rows,fields => 'ids',update_post_term_cache) before a custom cache class.Acorn specifics.
config:cache,view:cacheandoptimizespeed up the Laravel side only; they do not change WordPress queries. After a PHP change run{{runner.cache}}so a stale compiled view or manifest does not distort the measurement.Re-measure with the same URL and cache state (cold and warm) and report the delta.
Profile differences
| Topic | sage-theme |
acorn-mu-plugin |
radicle-app |
|---|---|---|---|
| Where to look for slow code | theme app/, composers on every view |
plugin app/, providers booted on after_setup_theme |
root app/, providers from composer.json extra.acorn.providers |
--path for the runner |
the WordPress core dir inside the container | same | the Bedrock-style core dir (often public/wp) |
| Object cache | project-chosen drop-in | project-chosen drop-in; global cache groups matter on multisite | Redis service in the landofile when present |
| Livewire cost | not present | present: each component adds a request and a hydration | absent by default |
Verification
perf_inspect.mjsprints JSON withwpCli.available: true(otherwise the runner value is wrong or the containers are down).- The same URL measured twice with the same cache state gives a comparable baseline and a smaller number after the change.
{{runner.wp}} cache typenames the expected backend; a persistent backend survives two consecutive CLI calls.
Failure modes
| Symptom | Cause | Fix |
|---|---|---|
perf_inspect.mjs reports wpCli.available: false or ENOENT |
Script ran a bare wp on the host |
Set WP_RUNNER="{{runner.wp}}"; start the containers |
WordPress not detected at the given --path |
Host path passed to a containerised WP-CLI | Use the path as the container sees it |
| Numbers differ wildly between runs | Cold vs warm object cache, or compiled views rebuilt | Run {{runner.cache}} once, then measure warm twice |
| Data stays stale after a fix | Persistent cache kept the old value, or an edge cache answers | {{runner.wp}} cache flush; check response headers for an edge cache age |
| Speed-up disappears in another environment | Object cache disabled there by a constant or env value | Compare the cache type in both environments before concluding |
wp profile shows nothing |
Package missing in the container | Install the package through the runner, or fall back to Query Monitor headless (official query-monitor-headless.md) |
Escalation
- Cloud or APM-level evidence (traces, edge cache behaviour) is outside this skill: ask for the APM export or the response headers instead of guessing.
- A slow query that needs a new index or schema change: stop and ask for approval; write the proposal, not the migration.
- If a project-level guideline contradicts an official reference, follow the project and say so.