Index of every page on this site: llms.txt

Performance

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 for up to 72 hours. Its default memory cache limits stored entries to 64 MiB. It 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

The default cache evicts the least recently used entries when it reaches its limit. Evicted images render again on request. The cache is lost on server restart.

To keep cached images across restarts, configure Nitro's cache storage through nitro.storage.cache. Redis and Cloudflare KV support automatic expiry. The module uses your configured driver instead of its bounded default. Use runtimeCacheStorage for a separate mount or a different memory limit.

Append ?purge=<token> to an OG image URL to drop its internal cache entry. The token is the URL signing secret.

See the Runtime Cache guide for storage drivers, cache keys, and TTL options.

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
// ❌ 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:

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>
Prerendered images are unaffected. The module automatically switches to short hash-based URLs (/_og/s/o_1whacq.png) for any path over 200 characters, or any path that holds a comma, a plus sign, or another reserved character.

Optimize Components

  • Avoid dynamic images: use static, statically-analyzable 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.

Prerender

Images generated at build time have zero runtime cost.

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)
  • Zero Runtime: removes all renderer code from production (81% smaller). See Zero Runtime.

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

Was this page helpful?