---
title: "Performance"
description: "Optimise OG image generation for fast rendering and minimal HTML overhead."
canonical_url: "https://nuxtseo.com/docs/og-image/guides/performance"
last_updated: "2026-05-06T18:44:25.916Z"
---

A first render takes 400-3500ms depending on renderer, but a cached render drops to 5-30ms - a 10-50x speedup. Caching is the single biggest win.

## Caching

The module caches rendered images in [Nitro's default cache storage](https://nitro.build/guide/cache) with a 72-hour TTL, and emits immutable `Cache-Control` headers so CDNs can serve cached copies without revalidation:

- **First render**: 400-1500ms (Satori), 600-2500ms (Takumi), 1000-3500ms (Browser)
- **Cached render**: 5-30ms

Nitro's default storage is in-memory, which means the cache is **lost on server restart**. For production, point Nitro's `cache` storage at a persistent driver (for example [Redis](https://redis.io) or Cloudflare KV) via `nitro.storage.cache`; the module picks it up automatically. Or use the module-specific `runtimeCacheStorage` option if you want OG images on a separate mount. Append `?purge` to any OG image URL to manually invalidate its cache entry.

<callout icon="i-heroicons-arrow-right" to="/docs/og-image/guides/runtime-cache">

See the [Runtime Cache guide](/docs/og-image/guides/runtime-cache) for storage drivers, cache keys, and TTL options.

</callout>

## Reduce URL Size

Runtime URLs encode all options in the path. Passing many props produces long URLs, adding HTML bloat to every page. To keep URLs short:

- **Pass minimal props**: send a slug or ID and `$fetch` the full data inside your component
- **Use module defaults**: options in `ogImage.defaults` are excluded from URLs
- **Use route rules**: options resolved server-side, never encoded in the URL

```ts
// ❌ All data as props — long URL
defineOgImage('BlogPost', {
  title: post.title,
  description: post.description,
  author: post.author.name,
  category: post.category,
})

// ✅ Just a slug — short URL
defineOgImage('BlogPost', { slug: post.slug })
```

Then fetch the data inside the component:

```vue [components/OgImage/BlogPost.takumi.vue]
<script setup lang="ts">
const { slug } = defineProps<{ slug: string }>()
const post = await $fetch(`/api/posts/${slug}`)
</script>

<template>
  <div class="w-full h-full flex flex-col justify-center p-12 bg-white">
    <h1 class="text-6xl font-bold">
      {{ post.title }}
    </h1>
    <p class="text-2xl text-gray-600 mt-4">
      {{ post.description }}
    </p>
  </div>
</template>
```

<callout icon="i-heroicons-light-bulb">

**Prerendered images are unaffected.** The module automatically switches to short hash-based URLs (`/_og/s/o_1whacq.png`) for any path exceeding 200 characters.

</callout>

## Optimise Components

- **Avoid dynamic images**: use static, statically-analysable image sources where possible. Dynamic image URLs trigger runtime fetches (100-500ms each) and dimension detection. The build step can resolve static sources ahead of time.
- **Keep font sets static**: each unique font combination forces Satori to re-parse font binaries (~2x throughput hit). Use a consistent set across components.
- **Use local emoji packages**: default emoji rendering fetches from a remote API each render. See the [Emojis guide](/docs/og-image/guides/emojis).

## Prerender

Images generated at build time have zero runtime cost.

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  routeRules: {
    '/blog/**': { prerender: true }
  }
})
```

- **Short URLs**: hash mode keeps URLs compact regardless of prop count
- **Build cache**: persists across deploys ([Build Cache guide](/docs/og-image/guides/build-cache))
- **Zero Runtime**: removes all renderer code from production (81% smaller). See [Zero Runtime](/docs/og-image/guides/zero-runtime).

Even if you can't prerender everything, consider it for high-traffic pages.
