Introduction
Nuxt OG Image v7 ships in Nuxt SEO v6. See the Nuxt SEO v6 release notes.
V7 requires Nuxt 4.6.1+ or Nuxt 5. It signs image URLs with your Nuxt app secret, uses native Satori layout, and reads fonts from @nuxt/fonts v1.
It can also publish rendered images to object storage.
Follow the v6 to v7 migration guide to upgrade.
Quick migration:
pnpm add nuxt-og-image@^7
npx nuxt-og-image generate-secretSet the printed NUXT_APP_SECRET value in your production environment.
Features
Publish Images to Object Storage
The module can upload rendered images to a public bucket through a Nitro storage mount. Prerendered og:image tags then use the bucket URL. (#730)
export default defineNuxtConfig({
ogImage: {
publish: { storage: 'og-image-public', baseURL: 'https://images.example.com' },
},
nitro: {
storage: {
'og-image-public': { driver: 'cloudflare-r2-binding', binding: 'OG_IMAGES' },
},
},
})A runtime image request redirects to the published copy when it exists. Otherwise, the server returns the image and uploads it in the background.
Prerendered local images now use app.cdnURL when you set it. See the publish option.
One Secret for Signing
URL signing now derives its key from the Nuxt 4.6 app secret. One NUXT_APP_SECRET value covers OG images, sessions, and other Nuxt features. (#729)
npx nuxt-og-image generate-secret
# NUXT_APP_SECRET=...See the Security guide.
Native Satori Layout
Satori 0.47.1 supports block, inline, flexbox, and grid layout. Templates now use this native layout. (#736)
The module now passes translate, rotate, scale, inset, z-index, and backdrop utilities to Satori.
@nuxt/fonts v1 Integration
The module reads fonts through the fonts:resolved hook of @nuxt/fonts 1.0. (#698)
- OG images can use every resolved family and weight, including families that only your site CSS uses.
- Bold text works on the webpack and rspack builders.
- Takumi loads OTF and WOFF files, as well as TTF and WOFF2.
- Satori converts static WOFF2 files and resolves static alternatives for variable fonts.
Performance
- SSR client builds no longer include the static URL encoder, the component manifest, or URL hashing. (#723)
- On an edge benchmark, cold import CPU fell from 119.8 ms to 114.1 ms, and RSS fell from 50.8 MiB to 47.4 MiB. (#720)
Breaking Changes
Nuxt 4.6.1 and Node 22 Required
V7 drops Nuxt 3 and Nuxt 4 versions before 4.6.1. (#720, #739)
| Dependency | v6 | v7 |
|---|---|---|
| Nuxt | >=3.16.0 | ^4.6.1 || ^5.0.0 |
| Node.js | >=18.0.0 | ^22.22.3 || ^24.15.0 || >=26.0.0 |
@unhead/vue | ^2.0.5 || ^3.0.0 | ^3.4.2 |
unstorage | ^1.15.0 | ^1.17.5 |
Signing Key From NUXT_APP_SECRET
V6 generated a new secret on each build unless you set one. V7 derives the key from Nuxt runtimeConfig.appSecret. (#729)
Production needs a root secret with at least 32 characters. If it is missing or invalid, OG image requests fail with a 500 error. (#742)
// Before: remove this
export default defineNuxtConfig({
ogImage: { security: { secret: process.env.NUXT_OG_IMAGE_SECRET } },
})# After
NUXT_APP_SECRET=<output of npx nuxt-og-image generate-secret>- V7 still reads
NUXT_OG_IMAGE_SECRETwhen the app secret is empty, and warns once. - V7 removes string values of
ogImage.security.secretandruntimeConfig.ogImage.secret.security.secret: falsestill turns off signing. - All v6 signed URLs become invalid. Rebuild prerendered pages and purge cached HTML.
Satori 0.47.1 Required
The satori peer is now ^0.47.1. The module no longer adds display: flex and flex-direction: column to a bare <div>. (#736)
<!-- Before -->
<div class="w-full h-full justify-between p-8">
<!-- After -->
<div class="box-border flex flex-col w-full h-full justify-between p-8">Text elements no longer get flex wrapping or a 0.2em gap. Templates may also need box-border, border-solid, explicit heading margins, or shrink-0 on SVGs.
The migration guide lists each layout change.
@nuxt/fonts 1.0 Required for Custom Fonts
With @nuxt/fonts before 1.0, OG images use the bundled Inter font and the build warns. (#698)
pnpm add @nuxt/fonts@^1The fontless peer is now ^1.3.2, and the unifont peer is now ^1.0.3. If OG text uses a script outside the default subsets, set subsets on each family.
Optional Peers Updated and Takumi v1 Removed
V7 requires these optional peer ranges. (#739)
| Package | v7 range |
|---|---|
@takumi-rs/core, @takumi-rs/wasm | ^2.15.0 |
@resvg/resvg-js, @resvg/resvg-wasm | ^2.6.2 |
sharp | ^0.35.5 |
playwright-core | ^1.64.0 |
chrome-launcher | ^1.2.2 |
tailwindcss | ^4.3.3 |
@unocss/core, @unocss/config | ^66.10.5 |
zod | ^4.6.6 |
The Takumi fetchedResources and persistentImages options merge into images. Each entry keeps its src and data fields.
// Before
defineOgImage('Card', props, { takumi: { fetchedResources: [{ src, data }], persistentImages: [{ src, data }] } })
// After
defineOgImage('Card', props, { takumi: { images: [{ src, data }] } })chrome-launcher Is an Optional Peer
The module no longer installs chrome-launcher. Satori and Takumi apps do not need it. (#749)
If you use the browser renderer with a local Chrome, install it as a dev dependency:
pnpm add -D chrome-launcherWithout it, development and local builds download Chromium through Playwright on the first render.
The browser renderer still needs playwright-core. Without it, the build fails and prints the install command.
This applies when you set ogImage.browser or a page uses a .browser.vue template.
Server Helpers Use Nuxt's RequestEvent
getOgImageUrl(event, ...) now takes Nuxt's portable RequestEvent. In a handler that calls it, import defineEventHandler from nuxt/server. (#720)
// Before: the h3 defineEventHandler auto-import
// After
import { defineEventHandler } from 'nuxt/server'
import { getOgImageUrl } from '#og-image/server'
export default defineEventHandler(event => ({
url: getOgImageUrl(event, '/blog/hello', { component: 'OgImagePost', props: { title: 'My post' } }),
}))For explicit app imports, use #og-image/app.
DevTools Panel Needs nuxtseo-devtools-host
The OG Image DevTools panel moved to an optional host package. (#720)
pnpm add -D nuxtseo-devtools-host@^1You do not need to add it to modules. Production rendering does not need it.
Bounded Runtime Cache
With the default runtimeCacheStorage: true and no Nitro cache mount, the image cache is now a 64 MiB LRU cache. (#726, #727)
runtimeCacheStorage: falsenow bypasses storage completely.- Storage drivers receive a TTL of
cacheMaxAgeSeconds, with a minimum of 60 seconds. (#725)
See the Runtime Cache guide.
Bug Fixes
- Escape commas and plus signs in dynamic image URLs, so a client cannot cut off the signature (#733)
- Use the render timeout for storage lookups when publishing (#731)
- Respect
app.baseURLin DevTools font previews (#738) - Pass the cache TTL to the storage driver, by @0xgdr (#725)
- Declare Site Config as a native module dependency (#734)