Requires Nuxt ^4.6.1 || ^5.0.0 and Unhead 3.4.2 or newer.
The module renders a Vue component to a PNG and adds og:image and twitter:image meta for the page. Docs: https://nuxtseo.com/docs/og-image
Setup
- Included in
@nuxtjs/seov6. If the app uses@nuxtjs/seo, configure theogImagekey and do not add the module again. - Otherwise, install it with
npx nuxt module add nuxt-og-image. - Set
site.url. Without it, prerendered pages get a relativeog:imagesuch as/_og/s/o_x.png. The build warns, but still succeeds. Crawlers need an absolute URL. - Install one renderer yourself:
@takumi-rs/core(recommended), orsatoriwith@resvg/resvg-js. Use the Wasm packages (@takumi-rs/wasm,@resvg/resvg-wasm) on edge runtimes.pnpm exec nuxt-og-image enable takumiinstalls the right one. - If a page or route rule uses a template whose renderer is not installed, the production build fails with the install command. A template that nothing uses only logs a warning. In dev, the module asks before it installs in an interactive terminal. In an agent, CI, or piped shell it only logs the command and never edits
package.json. - With no template and no renderer installed, OG images are off, and the module logs
npx nuxt-og-image enable takumi. - The module needs SSR. With
ssr: falseit warns and does nothing.
Automatic behaviour
- Only pages that call
defineOgImage()get OG image meta. A page with its ownuseSeoMeta({ ogImage })keeps it. defineOgImage,defineOgImageScreenshot, andgetOgImagePathare auto-imported.getOgImageUrlis auto-imported in server code.- Any
.vuefile incomponents/OgImage/is a template. The filename suffix picks the renderer:Card.takumi.vue,Card.satori.vue,Card.browser.vue. A file without a suffix is invalid. There is no renderer option. - Default size is 1200 by 600, not 1200 by 630. Set
ogImage.defaults.heightto change it. - Runtime URLs are signed. The module derives a signing secret from Nuxt
appSecret. SetNUXT_APP_SECRETin production; Nuxt persists a generated development secret. A tampered URL, or a URL without a signature, returns 403 in production. Query overrides such as?title=Xare ignored. Dev and prerender skip the check. - A prerendered page gets a static file at
/_og/s/. A server rendered page gets/_og/d/with all props encoded in the path. - Inter 400 and 700 are bundled. Emoji use the
notoset and follow the size of the surrounding text.
Common tasks
Create a template, then call it from a page:
<!-- components/OgImage/Card.takumi.vue (app/components/ on Nuxt 4) -->
<script setup lang="ts">
const { title = 'My site', color = '#86efac' } = defineProps<{ title?: string, color?: string }>()
</script>
<template>
<div class="w-full h-full flex items-center justify-center bg-blue-500 border-solid border-[24px]" :style="{ borderColor: color }">
<h1 class="text-8xl font-bold text-white">
{{ title }}
</h1>
</div>
</template>
<!-- pages/index.vue -->
<script setup lang="ts">
defineOgImage('Card', { title: 'Hello' })
</script>
The second argument holds the component props. Image options such as width, alt, or cacheMaxAgeSeconds go in the third argument.
Tailwind classes work without the Tailwind module. Put values that come from props in :style. The module resolves theme classes at build time, so a class built from a prop, such as `bg-${tone}`, gets only the renderer's default palette: bg-red-500 renders, bg-primary-500 or bg-brand renders nothing.
Give every prop a default, so DevTools can preview the template.
If two variants share a name, 'Card' picks the first one. Use 'Card.takumi' to select one.
More than one image per page, for example a square one for WhatsApp:
defineOgImage('Card', { title: 'Hello' }, [
{ key: 'og' },
{ key: 'whatsapp', width: 800, height: 800 },
])
key: 'og' writes og:image and twitter:image. Any other key writes only og:image. key: 'twitter' writes only twitter:image.
Set options for a group of pages with route rules. ogImage: false removes the image, even when the page calls defineOgImage():
export default defineNuxtConfig({
routeRules: {
'/blog/**': { ogImage: { props: { title: 'Blog' } } },
'/private/**': { ogImage: false },
},
})
Keep runtime URLs short: pass a slug, then await $fetch() the data inside the template.
To use an existing image, call useSeoMeta({ ogImage: '/cover.png' }). The v5 defineOgImage({ url }) form is gone.
Fonts
Custom fonts load only through @nuxt/fonts, and each family needs global: true. CSS @font-face rules and font CDN links do not reach the renderer. Local fonts: Takumi reads woff2 and ttf; Satori reads woff, ttf, and otf.
export default defineNuxtConfig({
modules: ['@nuxt/fonts', 'nuxt-og-image'],
fonts: { families: [{ name: 'Roboto', weights: [400, 700], global: true }] },
})
Cloudflare
Takumi and Satori use Wasm on Workers. If images render with no text, the Worker has no ASSETS binding and cannot load fonts. Set nitro.cloudflare.deployConfig: true and deploy with wrangler --cwd .output deploy, or add an [assets] block to your own Wrangler config. The runtime cache is in memory; for KV set ogImage.runtimeCacheStorage: { driver: 'cloudflare-kv-binding', binding: 'OG_IMAGE_CACHE' }.
Guide: https://nuxtseo.com/docs/og-image/guides/cloudflare
Traps
- Community templates such as
NuxtSeowork only in dev. A dev page render copies the template intocomponents/OgImage/. A production build fails ondefineOgImage('NuxtSeo.takumi')until you eject it. A component name built at runtime is not checked at build; that image returns 500. Runpnpm exec nuxt-og-image eject NuxtSeo. It picks the variant for the renderer your app uses;eject NuxtSeo.takumioreject NuxtSeo.satorichooses one. zeroRuntime: trueremoves the/_og/d/handler. A page that is not prerendered gets noog:image. Prerender every page that callsdefineOgImage().defineOgImageScreenshot()needsogImage.browser. Without it, the production build fails. Setbrowser: trueand installplaywright-core, or set{ provider: 'cloudflare', binding: 'BROWSER' }. Use it for prerendered pages; most hosts cannot run a browser.defineOgImage()in a client only component throws in dev and renders nothing in production. Call it in page or layout setup, on the server.- A border class needs
border-solid. A width without a style draws nothing, as in CSS. - Rolling or multi instance deploys need a stable secret. Set
NUXT_APP_SECRETfrompnpm exec nuxt-og-image generate-secret. Changing the application secret invalidates existing signed URLs. - Legacy environment key: V7 accepts
NUXT_OG_IMAGE_SECRETif the application secret is empty, with a deprecation warning. Rename it toNUXT_APP_SECRET. The derived key still invalidates v6 signatures. - A wildcard route rule with
swr,isr, orcachebreaks/_og/routes. The module warns. Use narrower patterns such as/blog/**.
Version limits
v7 changes that v6 examples still show:
- Nuxt versions before 4.6.1 are not supported. Upgrade Nuxt to 4.6.1 or later.
- String
ogImage.security.secretandruntimeConfig.ogImage.secretare removed. SetNUXT_APP_SECRETfrompnpm exec nuxt-og-image generate-secret. If the production secret is missing or shorter than 32 characters, OG image requests return 500. - Satori 0.47.1+ uses native layout. A bare
<div>is no longer a flex column. Addflex flex-colwhere the template needs it. - Custom fonts need
@nuxt/fonts1.0+. With an older version, images fall back to Inter. - Takumi v1 is removed. Use
@takumi-rs/coreor@takumi-rs/wasm^2.15.0. Mergetakumi.fetchedResourcesandtakumi.persistentImagesintotakumi.images. defineOgImageSchema()needs Zod 4.getOgImageUrl(event, ...)takes the NuxtRequestEvent. ImportdefineEventHandlerfromnuxt/server, not the h3 auto-import.chrome-launcheris no longer installed. Without it, the browser renderer downloads Chromium instead of using a local Chrome. Runpnpm add -D chrome-launcher. Withoutplaywright-core, a browser renderer build fails.- The DevTools panel needs
nuxtseo-devtools-hostas a dev dependency:pnpm add -D nuxtseo-devtools-host@^1.
v6 changes that older examples still show:
defineOgImageComponent('X', props)is deprecated. UsedefineOgImage('X', props).<OgImage>and<OgImageScreenshot>components,ogImage.fonts,componentOptions, anddefaults.rendererare removed./__og-image__/image/is now/_og/d/, and/__og-image__/static/is now/_og/s/.pnpm exec nuxt-og-image migrate v6 --dry-runpreviews the renames.
The html option is deprecated, and security.strict removes it. Use a component.
Config
defaults: default image options, such aswidth,height,extension,emojis. Options equal to a default stay out of the URL.zeroRuntime(false): drop the runtime renderer. See the trap above.security.secret,security.strict(false):strictrequires NuxtappSecret, dropshtml, and restricts runtime images to the site host.browser: enables screenshots and.browser.vuetemplates.- Other options: https://nuxtseo.com/docs/og-image/api/config
Debug
- Nuxt DevTools has an OG Image tab with a live preview. The tab needs
nuxtseo-devtools-hostas a dev dependency. - In dev, the image at a URL is cached. After a template edit, append
?purgeto the image URL. ogImage.debug: trueadds/_og/debug.jsonand logs more. It warns if left on in production.