WordPress Performance FAQ
Why are admin-ajax.php and WordPress REST API requests still slow even though my frontend pages are cached?
If my public pages are served from page cache, shouldn't the site be fast overall? Or do AJAX and REST requests bypass the page cache and still execute PHP, WordPress core, active plugins, hooks, database queries, authentication/session checks, and endpoint-specific application logic on every request?
Yes. In most WordPress setups, page caching only accelerates requests that can be satisfied with a cached HTML file. admin-ajax.php and most WordPress REST API requests are fundamentally different: they usually bypass page cache and execute the full application stack on every request.
Here's what typically happens.
Cached page request
For a cached public page:
- Browser requests
/some-page/ - Cache layer (Nginx FastCGI Cache, Varnish, Cloudflare APO, WP Rocket, LiteSpeed Cache, etc.) finds cached HTML
- Cached HTML is returned immediately
- PHP often never runs
- WordPress core, plugins, themes, and database queries are skipped entirely
This is why a page might load in 20–100 ms from cache.
admin-ajax.php request
For a request to /wp-admin/admin-ajax.php:
- Request reaches PHP
- WordPress bootstrap begins
- Core files load
- Active plugins load
- Theme functions may load
- Hooks are registered
- User authentication/cookie checks may occur
- The requested AJAX action is located
- Action-specific code executes
- Database queries run if needed
- Response is generated
Even if the page that triggered the AJAX call was served entirely from cache, the AJAX request itself still performs all of this work.
A common misconception is:
"My homepage is cached, so the site is fast."
The homepage may be fast, but if it triggers five AJAX calls that each take 500 ms, the user still experiences slowness.
REST API requests
The same principle generally applies to WordPress REST API endpoints such as:
/wp-json/wp/v2/posts
/wp-json/my-plugin/v1/data
A REST request typically:
- Starts PHP
- Loads WordPress
- Loads plugins
- Registers REST routes
- Performs permission checks
- Runs endpoint callbacks
- Executes database queries
- Serializes JSON
- Returns the response
Page cache usually does not intercept these requests.
Why REST/AJAX can be slow despite a "fully cached" site
Common causes include:
1. Full WordPress bootstrap cost
Even a trivial endpoint may spend significant time just loading WordPress and plugins before your code runs.
For sites with many plugins:
WordPress bootstrap: 300 ms
Plugin loading: 400 ms
Endpoint logic: 50 ms
Total: 750 ms
The endpoint itself isn't slow—the application startup is.
2. Expensive database queries
Many AJAX/REST handlers perform queries such as:
WP_Query(...)
get_posts(...)
get_user_meta(...)
or custom SQL.
If those queries are unoptimized, every request pays the cost.
3. Authentication and capability checks
REST endpoints often run permission callbacks:
current_user_can(...)
wp_get_current_user(...)
Authenticated requests may trigger additional user lookups and metadata loading.
4. Plugin hook overhead
Many plugins hook into:
init
wp_loaded
rest_api_init
parse_request
template_redirect
Even if the endpoint doesn't use those plugins directly, their hooks may still execute during bootstrap.
5. No caching on API responses
A page cache might store:
/
but not:
/wp-json/my-plugin/v1/stats
/wp-admin/admin-ajax.php?action=load_data
As a result, every API request becomes a cache miss and hits PHP.
Can REST endpoints be cached?
Yes.
Many REST endpoints are actually easier to cache than AJAX endpoints.
Examples:
GET /wp-json/my-plugin/v1/public-data
can often be cached at:
- CDN level
- Reverse proxy level
- Object cache level
- Application level
If the response is public and identical for all users, it can frequently be cached for seconds, minutes, or hours.
Can admin-ajax.php be cached?
Usually not as easily.
Reasons:
- Uses POST frequently
- Often user-specific
- Often tied to sessions or logged-in users
- Many caching systems deliberately exclude it
This is one reason modern WordPress development often favors REST endpoints over admin-ajax.php for public data.
A useful mental model
Think of page cache and AJAX/REST as separate performance domains:
| Request Type | Served from page cache? | Runs PHP? | Loads WordPress? |
|---|---|---|---|
| Cached page HTML | Usually yes | Usually no | Usually no |
admin-ajax.php |
Usually no | Yes | Yes |
| REST API endpoint | Usually no (unless separately cached) | Yes | Yes |
So your understanding is correct:
Even when public pages are fully cached, AJAX and REST requests typically bypass the page cache and still execute PHP, WordPress core, active plugins, hooks, database queries, authentication/permission checks, and endpoint-specific logic on every request unless those endpoints are separately optimized or cached.
This is why performance investigations often reveal a site with a 50 ms cached page response but several 500–1500 ms AJAX/REST calls that dominate the actual user experience.
That makes sense. So for many admin-ajax.php and REST requests, the slow part may not be the endpoint callback itself, but the fact that WordPress, active plugins, hooks, route registration, authentication checks, and application bootstrap happen before the endpoint logic even runs.
But if the request already exposes a fairly specific context early - for example admin-ajax.php?action=..., wc-ajax=..., or a known /wp-json/... route - shouldn't we also ask whether the full WordPress/plugin runtime is necessary for that specific endpoint?
For unavoidable dynamic AJAX or REST requests that cannot simply be page-cached, would a deeper optimization layer be to classify the endpoint context early and prevent unrelated plugins, hooks, services, and PHP code from entering that request lifecycle in the first place?
Yes. Once you've exhausted traditional caching, that is often the next frontier of WordPress performance engineering.
The key insight is:
The question is no longer "Can this response be cached?" but "How much of WordPress actually needs to exist to generate this response?"
For many AJAX and REST requests, the answer is "much less than currently loads."
The architectural mismatch
WordPress was designed around a request lifecycle where nearly every request boots the entire application:
PHP start
↓
Load WordPress core
↓
Load all active plugins
↓
Register hooks
↓
Register post types
↓
Register taxonomies
↓
Register REST routes
↓
Initialize services
↓
Run request routing
↓
Execute endpoint callback
This model works well for page rendering, where almost every subsystem might be needed.
But many AJAX/REST endpoints look more like:
Get cart count
Check inventory
Fetch search suggestions
Return stock status
Return product price
Load notifications
Those tasks often need only a tiny fraction of the runtime.
Why endpoint-aware loading is attractive
Imagine an endpoint:
/wp-json/my-plugin/v1/cart-count
The callback may require:
- Database access
- WooCommerce session
- Current user
But may not require:
- SEO plugin
- Form builder
- Analytics plugin
- Membership plugin
- Page builder
- Translation plugin
- Marketing automation plugin
Yet all of them may still load.
On heavily customized sites, it's common to see:
Endpoint logic: 20 ms
Plugin bootstrap: 600 ms
Total request: 620 ms
The business logic is not the bottleneck.
The application initialization is.
This is already how large applications think
Modern frameworks often optimize around:
Route-aware dependency loading
Instead of:
Load everything
Then determine route
they try to move toward:
Determine route
Load only required services
Execute route
The earlier the system knows the request context, the more work it can avoid.
WordPress makes this difficult
The challenge is that plugins are not isolated modules.
Many plugins assume:
plugins_loaded
init
wp_loaded
will always run.
And they frequently:
- register hooks globally
- modify queries globally
- inject filters globally
- load service containers globally
- initialize caches globally
Even if the current request never needs them.
This creates a strong coupling between:
Plugin activation
and
Plugin loading
rather than:
Plugin activation
and
Plugin loading only when relevant
Endpoint classification is theoretically powerful
If the system can identify very early:
admin-ajax.php?action=get_stock
or
/wp-json/myplugin/v1/search
then it could potentially decide:
Required:
- Core database layer
- MyPlugin
- WooCommerce
Skip:
- SEO plugin
- Form plugin
- Page builder
- Analytics plugin
Now bootstrap time drops dramatically.
WooCommerce illustrates the issue
Many WooCommerce stores have requests like:
?wc-ajax=get_refreshed_fragments
?wc-ajax=checkout
?wc-ajax=update_order_review
Historically these endpoints became performance hotspots.
Not necessarily because:
get_refreshed_fragments
was computationally expensive.
But because each request inherited the cost of:
WordPress
+
WooCommerce
+
dozens of plugins
+
their hooks
+
their initialization
before reaching the actual endpoint code.
Practical optimization layers
Think of optimization as a stack:
Layer 1: Page cache
Avoid WordPress entirely.
Request → Cache → Response
Best outcome.
Layer 2: Endpoint response cache
Cache REST/AJAX responses.
Request → Endpoint cache → Response
Still avoids application work.
Layer 3: Object cache
Allow WordPress to run but avoid database work.
Request
↓
WordPress loads
↓
Redis/Memcached serves data
Bootstrap remains.
Database cost shrinks.
Layer 4: Endpoint-aware runtime reduction
Allow WordPress to run but reduce loaded code.
Request
↓
Classify endpoint
↓
Load only required components
↓
Execute endpoint
Bootstrap itself shrinks.
Layer 5: Dedicated micro-endpoints
Move certain operations completely outside WordPress.
Nginx
↓
Small PHP service
↓
Database/cache
This is effectively what many high-scale platforms eventually do for extremely hot paths.
The hard part: determining what is "unrelated"
This is where most attempts become challenging.
Suppose an endpoint appears unrelated to an SEO plugin.
But that plugin may have:
add_filter(...)
add_action(...)
that:
- modify permissions
- modify product visibility
- alter language selection
- alter query behavior
Disabling it may break behavior unexpectedly.
The performance opportunity is real, but dependency analysis becomes difficult because WordPress plugins often communicate indirectly through hooks.
A useful rule of thumb
For a typical WordPress site:
First ask:
- Can the page be cached?
- Can the endpoint response be cached?
- Can database work be cached?
Only after those are addressed does it make sense to ask:
"Why are 40 plugins loading for this endpoint when only 2 are actually needed?"
At that point you're no longer doing cache optimization; you're doing runtime architecture optimization. And on large WordPress installations, that can yield some of the biggest remaining performance gains because the dominant cost often becomes framework and plugin bootstrap rather than the endpoint's own business logic.
That makes sense. But does endpoint-aware runtime reduction necessarily require moving AJAX or REST functionality into separate micro-endpoints outside WordPress?
For many WordPress and WooCommerce sites, the endpoint itself may still need WordPress or WooCommerce context: authentication, sessions, cart state, user permissions, plugin data, or database access.
So could the more practical WordPress-native approach be:
identify the AJAX or REST endpoint context early → apply a known-safe runtime profile → load only the plugins and PHP subsystems needed for that endpoint → fall back to the full stack when uncertain?
In other words, instead of replacing WordPress for every slow endpoint, could a request-aware execution layer reduce unnecessary plugin/runtime participation inside WordPress itself?
Yes. In fact, for most WordPress and WooCommerce deployments, that's probably the more realistic and higher-leverage direction.
The choice is not merely:
A) Full WordPress
or
B) External microservice
There's a potentially very valuable middle ground:
Request arrives
↓
Classify request context early
↓
Apply a known-safe runtime profile
↓
Load only required WordPress components
↓
Execute endpoint
That preserves WordPress compatibility while reducing unnecessary bootstrap work.
Why this is attractive
Many AJAX and REST endpoints are not "standalone" enough to leave WordPress.
For example:
wc-ajax=update_order_review
may require:
- WooCommerce session
- Cart contents
- Customer object
- Shipping calculations
- Tax calculations
- User state
- Store settings
- Database access
Moving that into a separate service often recreates large portions of WooCommerce outside WooCommerce.
That's usually a poor tradeoff.
The real question becomes:
Does this request need all currently active plugins and runtime participants?
Often the answer is no.
What endpoint-aware runtime reduction could look like
Imagine a site with:
WooCommerce
SEO plugin
Page builder
Form plugin
Analytics plugin
Membership plugin
Search plugin
Translation plugin
A request arrives:
/?
wc-ajax=get_cart_totals
The runtime profile might declare:
Required:
✓ WordPress core
✓ WooCommerce
✓ Session handling
✓ Object cache
Optional:
✗ SEO plugin
✗ Page builder
✗ Form plugin
✗ Analytics plugin
The endpoint still runs inside WordPress.
The endpoint still has WooCommerce context.
The endpoint still uses standard APIs.
But the bootstrap footprint is dramatically smaller.
WordPress already contains some hints of this idea
The concept is not entirely foreign to WordPress.
Core already distinguishes:
wp_doing_ajax()
REST_REQUEST
is_admin()
WP_CLI
Many plugins already contain logic like:
if ( wp_doing_ajax() ) {
return;
}
or:
if ( defined( 'REST_REQUEST' ) ) {
// Skip frontend-only features
}
The problem is that these checks usually occur after the plugin has already loaded.
So:
Plugin file loaded
↓
Plugin bootstrap runs
↓
Plugin discovers request type
↓
Plugin disables some features
You still paid much of the startup cost.
The deeper optimization would move the decision earlier.
The real leverage point is plugin loading
For many sites:
WordPress core bootstrap: 100 ms
WooCommerce: 100 ms
Required plugin: 50 ms
20 unrelated plugins: 700 ms
-----------------------------------
Total: 950 ms
The largest gain is often not:
Optimize endpoint callback
but:
Avoid loading 20 plugins
for that request.
Runtime profiles are often safer than dynamic guessing
One of the biggest dangers is automatic dependency inference.
For example:
This endpoint looks unrelated to Plugin X
can be wrong.
Plugin X may provide:
- capability filters
- pricing adjustments
- localization
- tax modifications
- customer segmentation
through hooks that are not obvious.
A safer model is usually:
Endpoint
→ Explicit profile
→ Explicit plugin allowlist
rather than:
Endpoint
→ Automatic dependency detection
The latter tends to break sites.
The former is much more predictable.
REST routes are especially suitable
REST requests are often easier to classify than general frontend requests.
For example:
/wp-json/myplugin/v1/search
/wp-json/wc/store/cart
/wp-json/wp/v2/posts
already expose their route identity very early.
A runtime layer could potentially map:
Route A → Profile A
Route B → Profile B
Route C → Full stack
before most plugin initialization occurs.
WooCommerce is arguably where this matters most
WooCommerce stores frequently have:
- many plugins
- high AJAX volume
- uncached cart/checkout flows
- authenticated requests
The pages themselves may already be highly optimized.
The remaining bottleneck often becomes:
Repeated dynamic requests
×
Heavy bootstrap
For those sites, reducing bootstrap participation can be more impactful than shaving a few milliseconds off the endpoint callback itself.
The biggest technical challenge
The challenge is not detecting the endpoint.
That's relatively easy.
For example:
action=...
wc-ajax=...
/wp-json/...
is visible very early.
The challenge is proving that a reduced runtime profile is safe.
WordPress's hook architecture means plugins influence behavior indirectly.
A plugin that appears unrelated may actually alter:
- permissions
- pricing
- localization
- query results
- customer state
through filters that the endpoint depends on.
So the problem becomes less about routing and more about dependency management.
In principle, yes
Your proposed model is arguably the most practical long-term optimization path for many WordPress sites:
Request classification
↓
Known-safe runtime profile
↓
Selective plugin/subsystem participation
↓
Endpoint execution
↓
Fallback to full stack when confidence is low
It retains WordPress compatibility, preserves access to WooCommerce and plugin state, avoids rewriting business logic into external services, and directly attacks what is often the dominant cost of dynamic requests: loading large amounts of PHP code that have no meaningful role in servicing that specific endpoint.
This is exactly where LiteCache Rush applies its principle of Performance by Prevention. Cached frontend pages can be fast while admin-ajax.php, wc-ajax, and REST API requests remain slow because those requests often bypass page cache and still bootstrap WordPress, WooCommerce, plugins, hooks, services, authentication, sessions, and endpoint logic. Rush targets this missing layer: it identifies the AJAX, REST, or request context early and applies a known-safe runtime profile before the full plugin stack is loaded. The goal is not to replace WordPress with external microservices, but to keep WordPress and WooCommerce available while preventing unrelated PHP and plugin execution from entering endpoint-specific requests in the first place.