---
title: "Runtime Cache"
description: "How OG image caching works at runtime, including internal storage, CDN edge caching, and platform integration."
canonical_url: "https://nuxtseo.com/docs/og-image/guides/runtime-cache"
---

> Index of every page on this site: [llms.txt](https://nuxtseo.com/llms.txt)

When generating images at runtime, Nuxt OG Image provides two layers of caching to minimize server load and response times. For most deployments, the defaults work well and require no configuration.

## Quick Start

The default runtime cache stores up to 64 MiB of image entries in memory. This does not cap total server memory.
When the cache reaches its limit, it evicts the least recently used images. Requests for evicted images render them again.

After 72 hours, a cached image is stale. The next request renders it again.

If you configure Nitro's `cache` storage, the module uses that driver instead. Named mounts and module-specific driver settings also take precedence.

For persistence across server restarts, configure Redis or Cloudflare KV:

::code-group
```ts [Cloudflare KV]
export default defineNuxtConfig({
  ogImage: {
    runtimeCacheStorage: {
      driver: 'cloudflare-kv-binding',
      binding: 'OG_IMAGE_CACHE'
    }
  }
})
```

```ts [Redis]
export default defineNuxtConfig({
  ogImage: {
    runtimeCacheStorage: {
      driver: 'redis',
      host: 'localhost',
      port: 6379
    }
  }
})
```

```ts [NuxtHub]
// Auto-detected when @nuxthub/core is installed with KV enabled
export default defineNuxtConfig({
  modules: ['@nuxthub/core', 'nuxt-og-image'],
  hub: { kv: true }
})
```
::

That's it. The module handles cache headers, CDN integration, and cache key generation automatically.

## How It Works

### Layer 1: Internal Cache (Nitro Storage)

The module caches rendered image buffers in Nitro storage. On a cache hit, Nitro serves the image in \~5-30ms instead of re-rendering (400-3500ms).

By default, this uses a **bounded in-memory** store under `cache/nuxt-og-image`. Other Nitro caches keep their own storage.
The cache is lost on server restart or redeployment. Configuring a [persistent storage driver](#persistent-storage) fixes this.

::warning
Explicit storage settings replace the bounded default. Choose a driver with a size limit or automatic expiry.
The `memory` driver in unstorage v1.17.5 ignores TTL and has no size limit.
Unread entries can remain until the server restarts. Newer driver versions may support expiry, but expiry alone does not bound memory.
Redis and Cloudflare KV support driver TTL. To change the memory limit, configure [`lru-cache`](https://unstorage.unjs.io/drivers/lru-cache):

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  ogImage: {
    runtimeCacheStorage: {
      driver: 'lru-cache',
      max: 0,
      maxSize: 128 * 1024 * 1024, // 128 MiB of stored entries
    },
  },
})
```
::

### Layer 2: CDN Edge Cache

Every response includes HTTP cache headers that instruct CDN proxies to cache at the edge:

```txt
Cache-Control: public, max-age=259200, s-maxage=259200, immutable
ETag: W/"<hash>"
Last-Modified: <timestamp>
Vary: accept-encoding, host
```

| Directive   | Purpose                                                                                                                     |
| ----------- | --------------------------------------------------------------------------------------------------------------------------- |
| `max-age`   | Browser cache duration                                                                                                      |
| `s-maxage`  | CDN/proxy cache duration                                                                                                    |
| `immutable` | Skip revalidation; URLs are content-addressed (params + component hash), so the response bytes never change for a given URL |

When the CDN has a cached copy, requests never reach your server.

### Auto-Configured Cache Headers

The module sets `Cache-Control` on every OG image response so CDNs cache it correctly without manual route rules:

| Endpoint                  | `Cache-Control`                                    |
| ------------------------- | -------------------------------------------------- |
| `/_og/s/**` (prerendered) | `public, max-age=31536000, immutable`              |
| `/_og/d/**` (dynamic)     | `public, max-age=<ttl>, s-maxage=<ttl>, immutable` |
| `/_og/r/**` (resolver)    | Same as dynamic                                    |

Where `<ttl>`{lang="html"} means `cacheMaxAgeSeconds` (default 72 hours). URLs use content addressing (encoded params + component hash), so `immutable` is safe; the response bytes never change for a given URL. You do not need a Nitro `swr`/`isr` route rule (and it wouldn't work anyway, as `cachedEventHandler` JSON-serializes responses and breaks binary PNG/JPEG output).

## Query Parameters

OG image URLs encode all options in the **URL path**, not query parameters:

```txt
/_og/d/w_1200,h_600,c_NuxtSeo,title_Hello+World.png
```

The cache key is deterministic from the path alone. Appending query parameters like `?ref=twitter` or `?utm_source=og` directly to an OG image URL has no effect on rendering. If you need different image variants, use a [custom cache key](#custom-cache-key).

Note that the generated OG image URL encodes your **page URL's** query parameters (e.g., `/products?page=2`) via the `_query` field. This means different page query strings produce different OG image URLs, since the page may render differently with different query params. Each unique combination gets its own cached image.

## Persistent Storage

The bounded default cache is lost on server restart. For persistent storage, you have two options:

### Option 1: Configure Nitro's `cache` storage (recommended)

The module writes cache entries under Nitro's `cache:` namespace, so pointing Nitro at a persistent driver is enough; no module-specific config needed:

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  nitro: {
    storage: {
      cache: {
        driver: 'redis',
        host: 'localhost',
        port: 6379,
      },
    },
  },
})
```

This also benefits anything else in your app that caches via Nitro (e.g. `cachedFunction`, `cachedEventHandler`, SWR route rules).

### Option 2: `runtimeCacheStorage`

If you want OG images on a separate mount from the rest of your Nitro cache, use `runtimeCacheStorage`, which accepts the same configuration as Nitro's [`storage`](https://nitro.build/guide/storage) option.

### Using Runtime Config

If you need environment-specific configuration (e.g., different [Redis](https://redis.io) hosts per environment), mount your own storage driver and reference it by key:

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  ogImage: {
    runtimeCacheStorage: 'og-image-cache'
  }
})
```

Then mount the storage in a Nitro plugin:

```ts [server/plugins/og-image-cache.ts]
import redisDriver from 'unstorage/drivers/redis'

export default defineNitroPlugin(() => {
  const config = useRuntimeConfig()

  const driver = redisDriver({
    base: 'og-image',
    host: config.redis.host,
    port: config.redis.port,
    password: config.redis.password,
  })

  useStorage().mount('og-image-cache', driver)
})
```

## Cache Time

By default, a cached image becomes stale after 72 hours (3 days).
Use `cacheMaxAgeSeconds` to change this duration globally or per image.
The bounded memory cache can evict an image sooner.

### Global Default

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  ogImage: {
    cacheMaxAgeSeconds: 60 * 60 * 24 // 1 day
  }
})
```

This controls when cached images become stale and the HTTP `Cache-Control` duration (`max-age` / `s-maxage`).
The driver also receives a TTL of at least 60 seconds, Cloudflare KV's minimum.
Drivers that support expiry remove unread entries automatically. Other drivers rely on read-time expiry or eviction.

### Per Image

```ts
defineOgImage('NuxtSeo.takumi', { title: 'Hello' }, { cacheMaxAgeSeconds: 30 })
```

## Custom Cache Key

For full control over caching, provide a custom cache key:

```ts
defineOgImage('NuxtSeo.takumi', { title: 'Hello' }, { cacheKey: 'my-custom-key' })
```

This is useful when you need to cache based on criteria that aren't part of the URL.

## Cache Version

By default, the cache is namespaced with the module version. Upgrading `nuxt-og-image` automatically invalidates all cached images.

To persist cache across module upgrades, set a static version or disable versioning:

::code-group
```ts [Static Version]
export default defineNuxtConfig({
  ogImage: {
    cacheVersion: 'v1',
  }
})
```

```ts [Disable]
export default defineNuxtConfig({
  ogImage: {
    cacheVersion: false
  }
})
```
::

## Purging the Cache

Append `?purge=<token>`{lang="html"} to an OG image URL to drop its internal cache entry and render the image again.

A purge uses the same signing secret as image URLs, so it needs [URL signing](https://nuxtseo.com/docs/og-image/guides/security#url-signing).
If the token is missing or wrong, the module answers with 403.
If you turn off signing, the module answers every purge with 403. The entry expires after `cacheMaxAgeSeconds`.
In development, the module has no runtime cache, so `?purge` has no effect on the server.

The purge token is the derived signing secret, not `NUXT_APP_SECRET`.
Obtain it in server code with `await deriveSecret('nuxt-og-image:url-signing')` from `nuxt/server`.
Keep this token private.

Note that this only clears the module's internal Nitro storage cache (Layer 1). CDN edge caches (Layer 2) may continue serving the previous version until their TTL expires. To force CDN re-fetch:

- **Vercel**: Redeploy, or use the [Vercel CLI](https://vercel.com/docs/cli) to purge
- **Cloudflare**: Purge via the dashboard or API
- **Social platforms**: Use our [Social Share Debugger](https://nuxtseo.com/tools/social-share-debugger) to force Twitter, Facebook, and LinkedIn to re-fetch

::callout{icon="i-heroicons-arrow-path" to="/tools/social-share-debugger"}
**Clear social platform caches**: Use our [Social Share Debugger](https://nuxtseo.com/tools/social-share-debugger) to force re-fetching.
::

## Disabling the Cache

While not recommended, you can disable caching entirely:

::code-group
```vue [Single Image]
<script lang="ts" setup>
defineOgImage('NuxtSeo.takumi', { title: 'Hello' }, {
  cacheMaxAgeSeconds: 0
})
</script>
```

```ts [Global]
export default defineNuxtConfig({
  ogImage: {
    runtimeCacheStorage: false,
  }
})
```
::

## Sitemap

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