In a Headless WordPress + Nuxt.js architecture, WordPress serves as the content backend providing data via REST API, while Nuxt.js handles frontend rendering. This separation brings flexibility and scalability, but also introduces a new challenge: every page request may trigger one or more WordPress API calls, slowing response times and placing unnecessary load on the server in high-traffic scenarios. This article systematically explores multi-layer caching strategies for optimizing WordPress REST API data in Nuxt.js projects.
Why Caching Matters
A typical Nuxt.js post listing page may need to hit three to four WordPress endpoints: posts (/wp/v2/posts), categories (/wp/v2/categories), tags (/wp/v2/tags), and media (/wp/v2/media). If every visitor triggers the full API call chain, server load grows linearly. Proper caching can reduce repeated request response times from hundreds of milliseconds to just a few milliseconds, delivering a dramatic user experience improvement.
Layer 1: Nuxt.js useFetch Caching
Nuxt 3's built-in useFetch and useAsyncData composables provide caching out of the box. The key parameter is the core of caching — requests sharing the same key execute only once during server-side rendering (SSR), and existing data is reused during client-side navigation.
// composables/usePosts.ts
export const usePosts = (page: number) => {
return useFetch('/wp/v2/posts', {
baseURL: 'https://api.example.com/wp-json',
key: `posts-page-${page}`,
query: { page, per_page: 10, _embed: true },
getCachedData: (key) => {
const nuxtApp = useNuxtApp()
const data = nuxtApp.payload.data[key]
if (!data) return
const age = Date.now() - (nuxtApp._cachedTime?.[key] || 0)
if (age < 5 * 60 * 1000) return data
}
})
}The code above implements a simple stale-while-revalidate pattern via getCachedData: cached data is returned directly within 5 minutes; after expiry, the next request triggers a background refresh.
Layer 2: Nitro Server Route Rules
Nuxt 3's Nitro server engine supports fine-grained caching strategies through routeRules configuration. This is the recommended approach — declare caching behavior for each route directly in nuxt.config.ts:
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
'/': { swr: 300 },
'/posts/**': { swr: 600 },
'/categories/**': { swr: 3600 },
},
nitro: {
storage: {
redis: {
driver: 'redis',
host: '127.0.0.1',
port: 6379
}
}
}
})The swr (stale-while-revalidate) parameter specifies cache duration in seconds. Nitro returns cached content during the valid period; after expiry, it returns stale content while regenerating fresh content in the background. Switching Nitro's storage driver to Redis keeps cached data persistent across server restarts.
Layer 3: WordPress REST API Cache Control
Post-level caching optimization can also be applied on the WordPress side. The REST API doesn't send strong cache headers by default; we can add Cache-Control headers via the rest_post_dispatch filter:
add_filter('rest_post_dispatch', function($response, $server, $request) {
$method = $request->get_method();
if ($method !== 'GET') return $response;
$response->header('Cache-Control', 'public, max-age=300, s-maxage=600');
$response->header('Vary', 'Accept-Encoding');
return $response;
}, 10, 3);Combined with WordPress object caching plugins like Redis Object Cache, API response speed improves dramatically once database queries are cached.
Layer 4: CDN Edge Caching
For production, CDN is the outermost caching defense. Distributing Nuxt.js SSR output and static assets through Cloudflare, Vercel Edge Network, or Alibaba Cloud CDN caches content at edge nodes closest to users. With stale-while-revalidate response headers, the CDN can continue serving stale content even when the backend API is temporarily unavailable.
Practical Combined Strategy
A mature project's caching architecture should be: Browser Cache → CDN → Nitro/Node.js → WordPress Object Cache → MySQL Query Cache. Each layer intercepts requests as close to the user as possible:
- Development: useFetch key deduplication only, prevent duplicate SSR requests
- Staging: enable Nitro swr cache with short TTL (60-120 seconds)
- Production: combine Nitro Redis cache + CDN edge cache, 5-10 minute page-level TTL
Key Considerations & Conclusion
The core tension in caching is balancing data freshness against response speed. Sites with frequent content updates should shorten cache durations or adopt Webhook mechanisms to proactively purge relevant caches when WordPress publishes/updates posts. Pages containing personalized content (user dashboards, shopping carts) must be excluded from caching to prevent data leakage. By thoughtfully applying these multi-layer caching strategies, your Headless WordPress site will achieve access speeds comparable to pure static sites.




