HomeMethod

Knowledge

Web VitalsTechniquesSnippetsAnswersImpactsBottlenecks

REST API Cache

Cache WordPress REST API responses to serve repeated API requests without PHP or database execution.

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

  1. 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.

  2. 02

    Cache at the PHP layer with transients

    Add a rest_pre_dispatch or rest_request_before_callbacks filter 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.

  3. 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 appropriate Cache-Control: public headers in your REST responses — add headers via the rest_send_allow_origin_headers or a custom REST response filter.

  4. 04

    Invalidate cache on content changes

    Hook into save_post, delete_post, and edited_term to delete transients that correspond to affected endpoints. For menus, hook into wp_update_nav_menu. Granular invalidation ensures fresh data without requiring a full cache flush on every change.

Code Example

Before
// Every REST API request executes PHP + DB
// GET /wp-json/wp/v2/posts?per_page=10
// → PHP bootstrap + WP_Query execution: ~400ms
After
<?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.

Test this techniqueCache AnalysisRun the tool

Targeted fixes, not guesswork.

Start an Engagement
Support our family now