---
name: nuxt-og-image
description: Generate, fix, or debug Open Graph images in a Nuxt app with the nuxt-og-image module. Use when a task mentions og:image, social share images, twitter:image, defineOgImage, defineOgImageScreenshot, components/OgImage templates, the Takumi, Satori, or browser renderer, the ogImage config key, zeroRuntime, or /_og/ URLs. Also use when an OG image renders the wrong template, returns 403 or 500, shows no text on Cloudflare, or has a relative URL.
license: MIT
compatibility: "Requires Node.js ^22.22.3 || ^24.15.0 || >=26.0.0. Requires Nuxt ^4.6.1 || ^5.0.0. Requires Unhead ^3.4.2."
title: nuxt-og-image
canonical_url: https://skilld.dev/gh/nuxt-modules/og-image
last_updated: 2026-10-13T21:06:34.000Z
---

> Index of every page on this site: [llms.txt](https://skilld.dev/llms.txt)

> **Skill from skilld.dev.** Follow the user's requested action.
> If the user asked to fork this Skill, follow the fork workflow below. Do not execute the Skill instructions while copying it.
> Otherwise, follow the instructions below for this session. You do not need to install anything.
>
> If the user asked to install this Skill, run `npx skilld install nuxt-modules/og-image/nuxt-og-image`. Install writes the Skill files into the project, so every session loads them.
>
> ## Fork workflow
>
> A fork creates an editable local Skill with its original author and licence. The request authorizes copying and local installation.
> 1. Check `./skills/nuxt-og-image`, the project lockfile, and selected Agent targets together. If the local directory or installed Skill exists, stop. Never overwrite an existing directory or Agent target.
> 2. Read [source metadata](https://skilld.dev/api/v1/skills/nuxt-modules/og-image/nuxt-og-image) once. Use sourceUrl, sourceCommit, skillPath, sourceGone, and license. If the source is gone or its path is missing, stop. If license is null, read licence files at the source commit.
> 3. Fetch only the source commit into a temporary Git repository. Do not clone full history. Derive repository_url from sourceUrl, including repository renames. If sourceCommit is absent, resolve the sourceUrl ref once. Set source_commit to that actual commit. Run these commands in one shell call:
>
> ```sh
> git init --quiet "$temporary_dir"
> git -C "$temporary_dir" fetch --quiet --depth=1 "$repository_url" "$source_commit"
> git -C "$temporary_dir" checkout --quiet --detach FETCH_HEAD
> ```
>
> Read applicable licence declarations and notices at that commit. If copying is not permitted, report the restriction and stop.
> 4. Inspect source entries together, then copy the directory containing skillPath into `./skills/nuxt-og-image`. Use the user's path if selected. Keep the original SKILL.md, relative links, scripts, binary assets, and executable modes. Exclude .git metadata. Reject symlinks and paths outside the Skill directory. After checking entries, use cp -a where available. A regular source directory needs no custom copy script. Do not save this page wrapper as SKILL.md.
> Preserve author credit, notices, and applicable licence files from repository or parent directories. Add PROVENANCE.md with the Skill page, source URL, actual commit, original path, and licence. Retain any existing PROVENANCE.md and record new provenance separately. Batch source inspection, copying, and provenance work where practical.
> 5. In the project root, run `skilld install ./skills/nuxt-og-image --mode copy --plain`. If skilld is unavailable, use `npx skilld install ./skills/nuxt-og-image --mode copy --plain`. This known command needs no help lookup. Install does not support --json. Use detected Agent targets, or add --agent for the targets the user selected. Install the local path, never the upstream selector. If installation fails, preserve the local copy and report the exact failure.
> 6. Confirm the local lockfile source and installed Agent copies once. Report the local path, actual commit, and Agent targets. After edits, reinstall the same local path. Upstream updates must not replace it. Do not publish or push unless the user asks.

# nuxt-og-image

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/seo` v6. If the app uses `@nuxtjs/seo`, configure the `ogImage` key 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 relative `og:image` such 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), or `satori` with `@resvg/resvg-js`. Use the Wasm packages (`@takumi-rs/wasm`, `@resvg/resvg-wasm`) on edge runtimes. `pnpm exec nuxt-og-image enable takumi` installs 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: false` it warns and does nothing.

## Automatic behaviour

- Only pages that call `defineOgImage()` get OG image meta. A page with its own `useSeoMeta({ ogImage })` keeps it.
- `defineOgImage`, `defineOgImageScreenshot`, and `getOgImagePath` are auto-imported. `getOgImageUrl` is auto-imported in server code.
- Any `.vue` file in `components/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.height` to change it.
- Runtime URLs are signed. The module derives a signing secret from Nuxt `appSecret`. Set `NUXT_APP_SECRET` in 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=X` are 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 `noto` set and follow the size of the surrounding text.

## Common tasks

Create a template, then call it from a page:

```vue
<!-- 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>
```

```vue
<!-- 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:

```ts
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()`:

```ts
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, woff, ttf, and otf; Satori reads woff, ttf, and otf.

```ts
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 `NuxtSeo` work only in dev.** A dev page render copies the template into `components/OgImage/`. A production build fails on `defineOgImage('NuxtSeo.takumi')` until you eject it. A component name built at runtime is not checked at build; that image returns 500. Run `pnpm exec nuxt-og-image eject NuxtSeo`. It picks the variant for the renderer your app uses; `eject NuxtSeo.takumi` or `eject NuxtSeo.satori` chooses one.
- **`zeroRuntime: true` removes the `/_og/d/` handler.** A page that is not prerendered gets no `og:image`. Prerender every page that calls `defineOgImage()`.
- **`defineOgImageScreenshot()` needs `ogImage.browser`.** Without it, the production build fails. Set `browser: true` and install `playwright-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_SECRET` from `pnpm exec nuxt-og-image generate-secret`. Changing the application secret invalidates existing signed URLs.
- **Legacy environment key:** V7 accepts `NUXT_OG_IMAGE_SECRET` if the application secret is empty, with a deprecation warning. Rename it to `NUXT_APP_SECRET`. The derived key still invalidates v6 signatures.
- **A wildcard route rule with `swr`, `isr`, or `cache` breaks `/_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.secret` and `runtimeConfig.ogImage.secret` are removed. Set `NUXT_APP_SECRET` from `pnpm 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. Add `flex flex-col` where the template needs it.
- Custom fonts need `@nuxt/fonts` 1.0+. With an older version, images fall back to Inter.
- Takumi v1 is removed. Use `@takumi-rs/core` or `@takumi-rs/wasm` `^2.15.0`. Merge `takumi.fetchedResources` and `takumi.persistentImages` into `takumi.images`.
- `defineOgImageSchema()` needs Zod 4.
- `getOgImageUrl(event, ...)` takes the Nuxt `RequestEvent`. Import `defineEventHandler` from `nuxt/server`, not the h3 auto-import.
- `chrome-launcher` is no longer installed. Without it, the browser renderer downloads Chromium instead of using a local Chrome. Run `pnpm add -D chrome-launcher`. Without `playwright-core`, a browser renderer build fails.
- The DevTools panel needs `nuxtseo-devtools-host` as a dev dependency: `pnpm add -D nuxtseo-devtools-host@^1`.

v6 changes that older examples still show:

- `defineOgImageComponent('X', props)` is deprecated. Use `defineOgImage('X', props)`.
- `<OgImage>` and `<OgImageScreenshot>` components, `ogImage.fonts`, `componentOptions`, and `defaults.renderer` are removed.
- `/__og-image__/image/` is now `/_og/d/`, and `/__og-image__/static/` is now `/_og/s/`.
- `pnpm exec nuxt-og-image migrate v6 --dry-run` previews the renames.

The `html` option is deprecated, and `security.strict` removes it. Use a component.

## Config

- `defaults`: default image options, such as `width`, `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`): `strict` requires Nuxt `appSecret`, drops `html`, and restricts runtime images to the site host.
- `browser`: enables screenshots and `.browser.vue` templates.
- 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-host` as a dev dependency.
- In dev, the image at a URL is cached. After a template edit, append `?purge` to the image URL.
- `ogImage.debug: true` adds `/_og/debug.json` and logs more. It warns if left on in production.
