---
title: "Caching"
description: "Optimize performance with multi-layer caching"
canonical_url: "https://wpnuxt.com/guide/caching"
---
# Caching

> Optimize performance with multi-layer caching

Every composable call triggers a network request to your WordPress GraphQL API. Without caching, each page load costs ~200-500ms waiting for WordPress to respond. With caching enabled (the default), repeated requests resolve in ~1-5ms — making your site feel instant.

WPNuxt provides multiple layers of caching to achieve this. See [How WPNuxt Works](https://wpnuxt.com/getting-started/how-it-works) for where caching fits in the overall architecture.

## Cache Layers

<table>
<thead>
  <tr>
    <th>
      Layer
    </th>
    
    <th>
      Scope
    </th>
    
    <th>
      Duration
    </th>
    
    <th>
      Purpose
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Server (Nitro)
    </td>
    
    <td>
      All users
    </td>
    
    <td>
      Configurable
    </td>
    
    <td>
      Cache GraphQL responses server-side
    </td>
  </tr>
  
  <tr>
    <td>
      Client (GraphQL)
    </td>
    
    <td>
      Per browser
    </td>
    
    <td>
      Session
    </td>
    
    <td>
      Deduplicate identical queries during navigation
    </td>
  </tr>
  
  <tr>
    <td>
      Payload
    </td>
    
    <td>
      Per request
    </td>
    
    <td>
      Hydration
    </td>
    
    <td>
      Prevent refetch after SSR
    </td>
  </tr>
</tbody>
</table>

All layers work together automatically.

## Server-Side Caching

WPNuxt caches GraphQL responses server-side using Nitro route rules.

### How It Works

1. First request fetches from WordPress (~200-500ms)
2. Response is cached on the server
3. Subsequent requests use cache (~1-5ms)
4. With SWR enabled, stale content serves instantly while refreshing in background

### Configuration

```ts [nuxt.config.ts]
wpNuxt: {
  cache: {
    enabled: true,   // Default: true
    maxAge: 300,     // Default: 300 (5 minutes)
    swr: true        // Default: true
  }
}
```

<table>
<thead>
  <tr>
    <th>
      Option
    </th>
    
    <th>
      Default
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        enabled
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Enable/disable caching
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        maxAge
      </code>
    </td>
    
    <td>
      <code>
        300
      </code>
    </td>
    
    <td>
      Cache duration in seconds
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        swr
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Stale-while-revalidate
    </td>
  </tr>
</tbody>
</table>

## Client-Side Caching

WPNuxt enables client-side GraphQL caching by default. This deduplicates identical queries during client-side navigation.

### Per-Query Control

```ts
// Disable client caching for real-time data
const { data } = usePosts(undefined, { clientCache: false })

// Custom cache key for complex scenarios
const { data } = usePosts({ category: 'news' }, {
  cacheKey: `posts-news-${locale.value}`
})
```

<table>
<thead>
  <tr>
    <th>
      Option
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Default
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        clientCache
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Enable client-side caching
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        cacheKey
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      -
    </td>
    
    <td>
      Custom cache key suffix
    </td>
  </tr>
</tbody>
</table>

## Payload Caching (getCachedData)

WPNuxt optimizes SSR payload handling with a smart `getCachedData` implementation:

- **During hydration**: Uses payload data (no refetch needed)
- **Watch-triggered refetches**: Uses cached data when available
- **Manual refresh**: Always fetches fresh data

### Custom getCachedData

Override the default behavior for specific use cases:

```ts
const { data } = usePosts(undefined, {
  getCachedData: (key, nuxtApp, ctx) => {
    // Always fetch fresh
    if (ctx.cause === 'refresh:manual') {
      return undefined
    }
    // Use cached data
    return nuxtApp.payload.data[key]
  }
})
```

The `ctx.cause` can be:

- `'initial'` - First load
- `'refresh:manual'` - Called `refresh()`
- `'refresh:hook'` - Triggered by Nuxt hook
- `'watch'` - Reactive dependency changed

## Data Freshness Trade-offs

Caching makes your site fast, but it introduces a fundamental trade-off: **cached content may be stale**. Understanding this trade-off helps you choose the right caching strategy.

### With SWR enabled (default)

When a visitor requests a page, they immediately receive the cached version — even if it's older than `maxAge`. In the background, the server fetches fresh data from WordPress and updates the cache. The *next* visitor sees the updated content.

This means content can be up to `maxAge` seconds old (5 minutes by default). For most content sites, this is an excellent trade-off: visitors never wait for WordPress, and content stays reasonably fresh.

### Without SWR

When cache expires, the next visitor waits for a fresh WordPress response (~200-500ms) before seeing the page. Content is never older than `maxAge`, but some visitors experience slower page loads.

### Preview mode

Preview mode automatically bypasses all caching — editors always see the latest draft content. See [Preview Mode](https://wpnuxt.com/guide/preview-mode) for setup details.

### Choosing the right balance

For most WordPress content sites, the default configuration (`maxAge: 300`, `swr: true`) provides the best balance of speed and freshness. Content updates appear within 5 minutes, and visitors never wait for WordPress. If your site requires near-real-time updates, reduce `maxAge` or disable caching for specific queries. See [Recommended Strategies](#recommended-strategies) below for common configurations.

## When to Disable Caching

### Development

```ts
// See changes immediately
wpNuxt: {
  cache: {
    enabled: false
  }
}
```

### Real-Time Content

```ts
// Per-query: disable for live data
const { data } = useLiveComments(undefined, { clientCache: false })

// Global: short cache with no SWR
wpNuxt: {
  cache: {
    maxAge: 60,
    swr: false
  }
}
```

### Authenticated Content

Authenticated requests bypass server cache automatically (via Authorization header). For user-specific data, disable client caching. See also [Preview Mode](https://wpnuxt.com/guide/preview-mode) for how caching interacts with draft content previews.

```ts
const { data } = useViewer(undefined, { clientCache: false })
```

## High-Traffic Sites

```ts
wpNuxt: {
  cache: {
    maxAge: 3600,  // 1 hour
    swr: true
  }
}
```

## Recommended Strategies

<table>
<thead>
  <tr>
    <th>
      Site Type
    </th>
    
    <th>
      <code>
        maxAge
      </code>
    </th>
    
    <th>
      <code>
        swr
      </code>
    </th>
    
    <th>
      <code>
        clientCache
      </code>
    </th>
    
    <th>
      Why
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Blog / content site
    </td>
    
    <td>
      300–3600
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Content changes infrequently, SWR keeps it fresh
    </td>
  </tr>
  
  <tr>
    <td>
      News / frequently updated
    </td>
    
    <td>
      60
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Short cache + SWR = fresh content without latency
    </td>
  </tr>
  
  <tr>
    <td>
      Dashboard / real-time
    </td>
    
    <td>
      0
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Always fetch latest data
    </td>
  </tr>
  
  <tr>
    <td>
      E-commerce catalog
    </td>
    
    <td>
      300
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Product data is mostly stable
    </td>
  </tr>
</tbody>
</table>

## Cache Invalidation

### Manual Refresh

```ts
const { data, refresh } = usePosts()

// Force fresh data
await refresh()
```

### Reactive Invalidation

```ts
const category = ref('news')
const { data } = usePosts(
  { categoryName: category },
  { watch: [category] }
)

// Changing category triggers refetch
category.value = 'tech'
```

### Webhook Revalidation {#webhook-revalidation}

WPNuxt can automatically purge the server cache when content changes in WordPress. When configured, WordPress sends a POST request to your Nuxt app, which clears all cached GraphQL responses immediately — no waiting for `maxAge` to expire.

#### Setup

**1. Configure the revalidation secret**

Set the `WPNUXT_REVALIDATE_SECRET` environment variable (or configure it in `nuxt.config.ts`):

```ts [nuxt.config.ts]
wpNuxt: {
  cache: {
    revalidateSecret: process.env.WPNUXT_REVALIDATE_SECRET
  }
}
```

This registers a POST endpoint at `/api/_wpnuxt/revalidate`.

**2. Install the WordPress mu-plugin**

Copy [`wpnuxt-revalidate.php`](https://github.com/wpnuxt/wpnuxt/blob/main/docs/examples/wpnuxt-revalidate.php) to your WordPress `mu-plugins/` directory and add to `wp-config.php`:

```php [wp-config.php]
define('WPNUXT_FRONTEND_URL', 'https://your-nuxt-app.com');
define('WPNUXT_REVALIDATE_SECRET', 'your-secret');
```

The plugin automatically notifies your Nuxt app when posts are created, updated, or deleted.

**3. Vercel CDN purge (Vercel deployments only)**

On Vercel, GraphQL responses are cached at the CDN edge. WPNuxt tags all cached responses with `Vercel-Cache-Tag: wpnuxt`, enabling targeted cache purging via Vercel's tag invalidation API.

To enable CDN cache purging on revalidation, add a `VERCEL_TOKEN` environment variable in your Vercel project settings:

<table>
<thead>
  <tr>
    <th>
      Variable
    </th>
    
    <th>
      How to get it
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        VERCEL_TOKEN
      </code>
    </td>
    
    <td>
      Create at <a href="https://vercel.com/account/tokens" rel="nofollow">
        vercel.com/account/tokens
      </a>
    </td>
  </tr>
</tbody>
</table>

`VERCEL` and `VERCEL_PROJECT_ID` are provided automatically by Vercel at runtime. When `VERCEL_TOKEN` is set, the revalidation endpoint invalidates the `wpnuxt` cache tag, purging all cached GraphQL responses from Vercel's CDN.

#### Testing

```bash
curl -X POST https://your-site.com/api/_wpnuxt/revalidate \
  -H 'Content-Type: application/json' \
  -d '{"secret":"your-secret"}'

# Expected: {"success":true,"purged":0,"vercelPurged":true}
# Wrong secret: 401
```

### Server Cache Expiry

Server cache also expires automatically based on `maxAge`. With webhook revalidation configured, content updates appear immediately instead of waiting up to `maxAge` seconds.

## Related Pages

- **Performance** — Broader performance optimization strategies
- **Preview Mode** — How caching is bypassed for draft content
- **Configuration** — All cache configuration options


## Sitemap

See the full [sitemap](https://wpnuxt.com/sitemap.md) for all pages.
