REST API Cache
Cache WordPress REST API responses to serve repeated API requests without PHP or database execution.
Metrics
This technique can move up to 67% of LCP's share of the Lighthouse score.
This technique does not move TBT.
This technique does not move CLS.
This technique can move up to 67% of FCP's share of the Lighthouse score.
This technique can move up to 33% of SI's share of the Lighthouse score.
This technique can move up to 67% of TTFB's share of the Lighthouse score.
Overview
The WordPress REST API serves JSON responses for navigation menus, block editor data, custom post type queries, and application-layer requests from headless frontends, mobile apps, and JavaScript-heavy themes. Unlike full page cache (which caches complete HTML documents), REST API cache stores JSON payloads — allowing the API to serve repeated requests in milliseconds rather than executing PHP and database queries on every call. For sites using block themes, full-site editing, or headless architectures, REST API caching is as critical as page caching.
Impact grade, A+ to D: how much of the Lighthouse score this technique can move. Unaffected: TBT, CLS.
The Problem
Each WordPress REST API request that is not cached triggers a full PHP bootstrap, authentication checks, query execution, and JSON serialization — typically taking 200ms–2s depending on server load. A JavaScript-driven WordPress theme that fetches navigation, recent posts, and widget data via the REST API on every page load makes 3–5 uncached API calls per visit. Under any meaningful traffic load, this generates 5× the database and PHP pressure of a comparable HTML-based setup, while also delaying the data-driven renders that produce the LCP element.
Performance Impact
LCP
Significant
for JS-rendered pages, the LCP element depends on API response speed — caching directly cuts LCP
FCP
100–800ms
API responses used to render above-fold content arrive much faster from cache
SI
Moderate
faster API responses mean JS-rendered content fills the viewport sooner
TTFB
200ms–1.5s
cached API responses skip PHP execution entirely, matching page cache speed for API calls
Risk: Medium
stale cache may serve outdated content; authenticated endpoints must never be cached
How it works
01
Identify cacheable REST API endpoints
Public, unauthenticated endpoints are safe to cache:
/wp-json/wp/v2/posts,/wp-json/wp/v2/menus,/wp-json/wp/v2/categories. Authenticated endpoints (requests with nonces or auth cookies) must not be cached — they return user-specific data.02
Cache at the PHP layer with transients
Add a
rest_pre_dispatchorrest_request_before_callbacksfilter to check for a cached response before the route handler executes. On cache miss, let the handler run, then store the response in a transient keyed by the full request URL. On cache hit, return the stored JSON directly.03
Cache at the Nginx or CDN layer
Configure Nginx to cache REST API responses with a short TTL (1–15 minutes) for GET requests without authentication headers. CDNs can cache
/wp-json/paths if you set appropriateCache-Control: publicheaders in your REST responses — add headers via therest_send_allow_origin_headersor a custom REST response filter.04
Invalidate cache on content changes
Hook into
save_post,delete_post, andedited_termto delete transients that correspond to affected endpoints. For menus, hook intowp_update_nav_menu. Granular invalidation ensures fresh data without requiring a full cache flush on every change.
Code Example
// Every REST API request executes PHP + DB
// GET /wp-json/wp/v2/posts?per_page=10
// → PHP bootstrap + WP_Query execution: ~400ms<?php
// Cache REST API responses with transients
add_filter('rest_pre_dispatch', function($result, $server, $request) {
if ($request->get_method() !== 'GET') return $result;
$key = 'rest_cache_' . md5($request->get_route() . serialize($request->get_params()));
$cached = get_transient($key);
if ($cached !== false) {
return new WP_REST_Response($cached['data'], $cached['status']);
}
return $result; // proceed to handler
}, 10, 3);
add_filter('rest_post_dispatch', function($result, $server, $request) {
if ($request->get_method() !== 'GET' || is_wp_error($result->data)) return $result;
$key = 'rest_cache_' . md5($request->get_route() . serialize($request->get_params()));
set_transient($key, ['data' => $result->data, 'status' => $result->status], 10 * MINUTE_IN_SECONDS);
return $result;
}, 10, 3);This example caches all GET responses for 10 minutes. Add route-specific exclusions for authentication-sensitive endpoints and configure invalidation hooks per endpoint.
Limitations
- Authentication-sensitive endpoints (user data, drafts, private posts) must never be cached at the application or CDN layer — only cache responses that are identical for all unauthenticated users.
- REST API responses often include dynamically generated nonces or timestamps. Strip or exclude these from the cached payload to avoid serving expired security tokens.
- Caching nested or paginated REST responses (posts page 2, categories with 50 items) can produce many cache entries. Define per-endpoint TTLs and eviction policies based on the freshness requirements of each data type.
Common Mistakes
Caching REST endpoints without checking for authentication headers
Before storing a response in cache, check that the request has no Authorization header, no cookie header containing WordPress session cookies, and no nonce parameter. Any authenticated request must pass through to the handler without caching.
Setting the same TTL for all REST endpoints regardless of content freshness
Navigation menu data changes rarely — a 24-hour TTL is appropriate. Latest posts data changes frequently — a 5-minute TTL is safer. Tailor TTLs per endpoint based on how often the underlying data changes.