Runtime Cache · Nuxt OG Image · Nuxt SEO

-
-
-
-

[1.4K](https://github.com/harlan-zw/nuxt-seo)

[Nuxt SEO on GitHub](https://github.com/harlan-zw/nuxt-seo)

**OG Image v6** is here! Looking for an older version?

.

OG Image

-
-
-
-
-
-
-
-
-
-

Search…```k`` /`

v6 (latest)

- Playgrounds
- [Discord Support](https://discord.com/invite/275MBUBvgP)

### Getting Started

-
-
-
-

### Core Concepts

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

### Overview

-
-
-
-

### Integrations

-
-
-

Core Concepts

# Runtime Cache

[Copy for LLMs](https://nuxtseo.com/docs/og-image/guides/runtime-cache.md)

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

## [Quick Start](#quick-start)

The module caches by default with a 72-hour TTL. For production, the only recommended change is adding persistent storage so your cache survives server restarts:

Cloudflare KV

Redis

NuxtHub

```
export default defineNuxtConfig({
  ogImage: {
    runtimeCacheStorage: {
      driver: 'cloudflare-kv-binding',
      binding: 'OG_IMAGE_CACHE'
    }
  }
})
```

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

```
// 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](#how-it-works)

### [Layer 1: Internal Cache (Nitro Storage)](#layer-1-internal-cache-nitro-storage)

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

By default, this uses **in-memory** storage. The cache is lost on server restart or redeployment. Configuring a [persistent storage driver](#persistent-storage) fixes this.

### [Layer 2: CDN Edge Cache](#layer-2-cdn-edge-cache)

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

```
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](#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>` is `cacheMaxAgeSeconds` (default 72 hours). URLs are content-addressed (encoded params + component hash), so `immutable` is safe; the response bytes never change for a given URL. No Nitro `swr`/`isr` route rule is needed (and it wouldn't work anyway, as `cachedEventHandler` JSON-serializes responses and breaks binary PNG/JPEG output).

## [Query Parameters](#query-parameters)

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

```
/_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 your **page URL's** query parameters (e.g., `/products?page=2`) are encoded into the generated OG image URL 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](#persistent-storage)

The default in-memory cache is lost on server restart. For production you have two options:

### [Option 1: Configure Nitro's `cache` storage (recommended)](#option-1-configure-nitros-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:

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`](#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](#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:

nuxt.config.ts

```
export default defineNuxtConfig({
  ogImage: {
    runtimeCacheStorage: 'og-image-cache'
  }
})
```

Then mount the storage in a Nitro plugin:

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](#cache-time)

The default TTL is 72 hours (3 days). You can change it globally via `cacheMaxAgeSeconds` or per image.

### [Global Default](#global-default)

nuxt.config.ts

```
export default defineNuxtConfig({
  ogImage: {
    cacheMaxAgeSeconds: 60 * 60 * 24 // 1 day
  }
})
```

This controls the internal storage TTL and HTTP `Cache-Control` header duration (`max-age` / `s-maxage`).

### [Per Image](#per-image)

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

## [Custom Cache Key](#custom-cache-key)

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

```
defineOgImage('NuxtSeo', { 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](#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:

Static Version

Disable

```
export default defineNuxtConfig({
  ogImage: {
    cacheVersion: 'v1',
  }
})
```

```
export default defineNuxtConfig({
  ogImage: {
    cacheVersion: false
  }
})
```

## [Purging the Cache](#purging-the-cache)

Append `?purge` to any OG image URL to invalidate its internal cache entry and force a fresh render.

When

 is enabled, you must provide the signing secret as the purge value: `?purge=<your-secret>`. This prevents unauthorized cache invalidation.

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 to force Twitter, Facebook, and LinkedIn to re-fetch

**Clear social platform caches**: Use our

 to force re-fetching.

## [Disabling the Cache](#disabling-the-cache)

While not recommended, you can disable caching entirely:

Single Image

Global

```
<script lang="ts" setup>
defineOgImage('NuxtSeo', { title: 'Hello' }, {
  cacheMaxAgeSeconds: 0
})
</script>
```

```
export default defineNuxtConfig({
  ogImage: {
    runtimeCacheStorage: false,
  }
})
```

[Edit this page](https://github.com/nuxt-modules/og-image/edit/main/docs/content/3.guides/3.runtime-cache.md)

[Markdown For LLMs](https://nuxtseo.com/docs/og-image/guides/runtime-cache.md)

Did this page help you?

On this page

- [Quick Start](#quick-start)
- [How It Works](#how-it-works)
- [Query Parameters](#query-parameters)
- [Persistent Storage](#persistent-storage)
- [Cache Time](#cache-time)
- [Custom Cache Key](#custom-cache-key)
- [Cache Version](#cache-version)
- [Purging the Cache](#purging-the-cache)
- [Disabling the Cache](#disabling-the-cache)

[GitHub](https://github.com/harlan-zw/nuxt-seo) [ Discord](https://discord.com/invite/275MBUBvgP)

###

-
-

Modules

-
-
-
-
-
-
-
-
-

###

-
-
-

###

Nuxt

-
-
-
-
-

Vue

-
-
-
-
-
-
-
-

###

-
-
-
-
-
-
-
-
-
-

Copyright © 2023-2026 Harlan Wilton - [MIT License](https://github.com/harlan-zw/nuxt-seo/blob/main/license) · [mdream](https://mdream.dev)