Runtime Cache
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:
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
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 fixes this.
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:
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:
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> 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:
/_og/d/w_1200,h_600,c_NuxtSeo,title_Hello+World.pngThe 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.
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:
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 option.
Using Runtime Config
If you need environment-specific configuration (e.g., different Redis hosts per environment), mount your own storage driver and reference it by key:
export default defineNuxtConfig({
ogImage: {
runtimeCacheStorage: 'og-image-cache'
}
})Then mount the storage in a Nitro plugin:
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
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
defineOgImage('NuxtSeo.takumi', { title: 'Hello' }, { cacheMaxAgeSeconds: 30 })Custom Cache Key
For full control over caching, provide a custom cache key:
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:
export default defineNuxtConfig({
ogImage: {
cacheVersion: 'v1',
}
})export default defineNuxtConfig({
ogImage: {
cacheVersion: false
}
})Purging the Cache
Append ?purge=<token> 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.
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 to purge
- Cloudflare: Purge via the dashboard or API
- Social platforms: Use our Social Share Debugger to force Twitter, Facebook, and LinkedIn to re-fetch
Disabling the Cache
While not recommended, you can disable caching entirely:
<script lang="ts" setup>
defineOgImage('NuxtSeo.takumi', { title: 'Hello' }, {
cacheMaxAgeSeconds: 0
})
</script>export default defineNuxtConfig({
ogImage: {
runtimeCacheStorage: false,
}
})