WordPress Performance FAQ


Mike

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?

Sam

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:

  1. Browser requests /some-page/
  2. Cache layer (Nginx FastCGI Cache, Varnish, Cloudflare APO, WP Rocket, LiteSpeed Cache, etc.) finds cached HTML
  3. Cached HTML is returned immediately
  4. PHP often never runs
  5. 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:

  1. Request reaches PHP
  2. WordPress bootstrap begins
  3. Core files load
  4. Active plugins load
  5. Theme functions may load
  6. Hooks are registered
  7. User authentication/cookie checks may occur
  8. The requested AJAX action is located
  9. Action-specific code executes
  10. Database queries run if needed
  11. 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:

  1. Starts PHP
  2. Loads WordPress
  3. Loads plugins
  4. Registers REST routes
  5. Performs permission checks
  6. Runs endpoint callbacks
  7. Executes database queries
  8. Serializes JSON
  9. 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.

Mike

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?

Sam

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:

  1. Can the page be cached?
  2. Can the endpoint response be cached?
  3. 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.

Mike

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?

Sam

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.

Mike

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.