This guide covers upgrading from v6. If you use v5, follow the v6 migration guide first.
Update Node and Nuxt
Update your local, CI, and deployment environments before installing v7.
| Dependency | Supported versions |
|---|---|
| Node.js | `^22.22.3 |
| Nuxt | `^4.6.1 |
@unhead/vue | ^3.4.2 |
unstorage | ^1.17.5 |
Nuxt 3 and Nuxt 4 versions before 4.6.1 are no longer supported.
If you pin @unhead/vue or unstorage directly or through an override, update those pins too.
Nuxt 5 compatibility mode is optional. You do not need to enable it to use v7 on Nuxt 4.6.
From your application directory, update the module:
pnpm add nuxt-og-image@^7Keep the packages required by your renderer. If you use Satori, update it and review your templates as described below.
Update optional peers
Update the optional peers your application already installs, including any dependency overrides.
| Package | Required range |
|---|---|
@takumi-rs/core, @takumi-rs/wasm | ^2.15.0 |
satori | ^0.47.1 |
@resvg/resvg-js, @resvg/resvg-wasm | ^2.6.2 |
playwright-core | ^1.64.0 |
chrome-launcher | ^1.2.2 |
sharp | ^0.35.5 |
@unocss/core, @unocss/config | ^66.10.5 |
tailwindcss | ^4.3.3 |
nitropack | ^2.13.4 |
zod | ^4.6.6 |
See the font peer requirements in Update @nuxt/fonts.
Update Takumi
V7 requires Takumi 2.15.0 or later within v2. Takumi v1 is no longer supported. For native rendering, update the core package:
pnpm add @takumi-rs/core@^2.15.0For Wasm rendering, update the Wasm package instead:
pnpm add @takumi-rs/wasm@^2.15.0Replace takumi.fetchedResources and takumi.persistentImages with takumi.images.
If you used both arrays, merge their entries into images.
Each entry keeps its src and data fields.
Render your custom Takumi templates and compare their images before deploying. The renderer update can change text spacing and positioned layouts.
Update Zod for Nuxt Content
If you use defineOgImageSchema() from nuxt-og-image/content, update Zod to v4:
pnpm add zod@^4.6.6Zod v3 is no longer supported. The Content integration remains optional. See the Nuxt Content integration for collection schema setup.
Update Satori templates
V7 requires Satori 0.47.1 or later within 0.47. Update it alongside Nuxt OG Image:
pnpm add -D satori@^0.47.1Update your existing @resvg/resvg-js or @resvg/resvg-wasm package to 2.6.2 or later within v2.
If you use UnoCSS, its @unocss/core and @unocss/config peers require 66.10.5 or later within v66.
Update any older UnoCSS pins together.
Satori now supports block, inline, flexbox, and grid layout.
The module no longer adds flex styles to elements that omit them.
This can change existing .satori.vue templates and HTML passed to Satori.
Make flex layouts explicit
Previously, a bare <div> could receive display: flex and flex-direction: column automatically.
Set both properties when your template needs that layout.
For example, this v6 template relied on the automatic styles:
<template>
<div class="w-full h-full justify-between p-8">
<h1>My article</h1>
<div>example.com</div>
</div>
</template>In v7, declare the flex column:
<template>
<div class="box-border flex flex-col w-full h-full justify-between p-8">
<h1>My article</h1>
<div>example.com</div>
</div>
</template>For inline styles, use style="display: flex; flex-direction: column".
If your template needs a row, set flex-direction: row explicitly.
Check containers using gap, justify-content, or align-items without an explicit flex or grid display.
Text elements now use native text and inline layout.
The module no longer adds flex wrapping or a synthetic 0.2em gap to headings, paragraphs, and inline elements.
If your design relies on those styles, add flex flex-wrap gap-[0.2em] to the affected element.
Otherwise, let Satori lay out the text and inline children naturally.
Preserve dimensions and spacing
Native CSS defaults can change the geometry of existing templates. Set the styles your design needs, then compare rendered images.
| If the image changes | What to check |
|---|---|
| Padding or borders make a fixed-size box larger | Add box-border, or set box-sizing: border-box |
| A border disappears | Add border-solid, or set border-style: solid |
| Headings or paragraphs move | Set explicit margins, such as m-0, then add the intended spacing |
| An SVG or image shrinks inside a flex container | Add shrink-0, or set flex-shrink: 0 |
| A background image changes its crop or height | Set both dimensions of background-size explicitly |
For example, background-size: 300% now preserves the background image's aspect ratio.
If your old design used a stretched background, provide its intended height:
.profile-card {
background-size: 300% 187px;
}Use the height from your own template. The 187px value is an example from the module's fixture.
Review transforms and stacking
The module now passes translate, rotate, scale, and inset through to Satori.
It also preserves z-index and backdrop utilities that earlier versions filtered out.
Satori applies these properties natively.
If you combine individual transforms with transform, check their composed position, scale, and rotation.
Check overlapping elements and backdrop effects, since previously ignored styles can now change the output.
See Styling for supported layout and style options.
Compare images before deploying
Render every custom Satori template with representative text, images, emoji, and font weights. Compare both prerendered images and runtime images against your previous output. Use the rendered PNG for comparison; the HTML preview uses the browser's layout engine.
Fix layout differences with explicit template styles before accepting new snapshots. Keep your existing image comparison rules unless you intentionally change your project's visual tolerance.
Move signing to NUXT_APP_SECRET
V6 generated an OG image secret during each build unless you supplied one.
V7 derives its signing key from Nuxt's private runtimeConfig.appSecret, using the purpose nuxt-og-image:url-signing.
Production runtime requests require a root secret of at least 32 characters when signing is enabled.
If the secret is missing or invalid, OG image requests return 500. Pages still render.
The server logs the fix once per instance.
If your application already sets NUXT_APP_SECRET, keep that value.
Otherwise, generate a root secret:
pnpm exec nuxt-og-image generate-secretSet the printed NUXT_APP_SECRET value in your deployment environment.
On Cloudflare Workers, configure it as a secret binding named NUXT_APP_SECRET.
Keep the value private and stable across deployments and server instances within each environment.
V7 accepts NUXT_OG_IMAGE_SECRET when the application secret is empty.
This fallback also supports Cloudflare bindings and logs a deprecation warning once per runtime.
Rename the old environment key to NUXT_APP_SECRET when convenient. Keep its value unchanged.
A non-empty application secret always takes precedence. Invalid values still fail closed.
The fallback uses Nuxt's key derivation, so existing v6 signatures still become invalid.
Remove these old configuration settings if you use them:
- String values of
ogImage.security.secret runtimeConfig.ogImage.secret
For example, remove the module-specific key:
export default defineNuxtConfig({
ogImage: {
security: { secret: process.env.NUXT_OG_IMAGE_SECRET },
},
})Configure the root secret through your environment instead:
NUXT_APP_SECRET=<your-generated-root-secret>You do not need to declare runtimeConfig.appSecret in nuxt.config.ts.
Never put it under runtimeConfig.public or app.config.ts.
| Rendering mode | Root secret requirement |
|---|---|
| Production runtime, signing enabled | Set it on every server instance |
| Strict prerendering | Set the same value during the build and at runtime |
| Normal static prerendering | No root secret needed during prerendering |
zeroRuntime: true | No root secret needed unless strict prerendering produces signed URLs |
| Development | Nuxt generates and persists a development secret if none is configured |
ogImage.security.secret: false still disables signing. It cannot be combined with security.strict: true.
See Security before choosing unsigned runtime URLs.
Existing v6 signatures become invalid because the derived signing key differs from the old key.
Rebuild prerendered pages and refresh cached HTML that contains dynamic OG image URLs.
After upgrading, fetch a page's new og:image URL and confirm it returns an image.
Changing NUXT_APP_SECRET later also invalidates signatures, sessions, and other values derived from that root secret.
If you purge images with ?purge=<token>, use the derived OG image signing key, rather than the root secret.
If you use the legacy fallback, rename the environment key before using Nuxt's deriveSecret() directly.
In server code, derive it with:
import { deriveSecret } from 'nuxt/server'
const purgeToken = await deriveSecret('nuxt-og-image:url-signing')Keep this token private. On Workers, run administrative purge code where Nuxt can resolve the root secret. See Runtime Cache for CDN cache limits.
Update server imports
If your server handlers call getOgImageUrl, import defineEventHandler from nuxt/server.
On Nuxt 4, the auto-imported h3 handler supplies a different event shape.
The helper now accepts Nuxt's portable RequestEvent.
import { defineEventHandler } from 'nuxt/server'
import { getOgImageUrl } from '#og-image/server'
export default defineEventHandler((event) => {
return {
url: getOgImageUrl(event, '/blog/hello', {
component: 'OgImagePost',
props: { title: 'My post' },
}),
}
})Pass the current request event so signing can use request-specific bindings.
For your own event annotations, import RequestEvent from nuxt/server instead of using H3Event.
App composables remain auto-imported. For explicit imports, use #og-image/app:
import { defineOgImage } from '#og-image/app'Update @nuxt/fonts
Nuxt OG Image v7 reads fonts through the fonts:resolved hook of @nuxt/fonts.
Use @nuxt/fonts 1.0.0 or newer, and update both modules together.
If you install the optional font peers directly, use fontless 1.3.2+ and unifont 1.0.3+.
Keep global: true on every font family used by an OG image.
pnpm add @nuxt/fonts@^1If you pin the optional peers directly, update them too:
pnpm add fontless@^1.3.2 unifont@^1.0.3V7 removes support for fontless 0.x and unifont 0.x. Remove overrides that pin either package to those versions.
If your @nuxt/fonts version is older than 1.0.0, OG images use the bundled Inter font. The build reports:
@nuxt/fonts <version> is not supported. Version 1.0.0+ is required for OG image font extraction.OG images load the font files that @nuxt/fonts serves in development, prerendering, and runtime rendering.
The webpack and rspack builders can use each configured family and weight.
See Custom Fonts for configuration and renderer format support.
Check subsets and variable fonts
Configure every writing system your OG text needs in each family's subsets option.
The default subsets do not include every writing system. For example:
export default defineNuxtConfig({
fonts: {
families: [
{ name: 'Noto Sans Devanagari', global: true, subsets: ['devanagari', 'latin'] },
],
},
})Avoid glyphs on families that render arbitrary OG text. It limits the served files to those characters.
V7 attempts to resolve full alternatives. If that fails, it keeps the subset files and warns about missing characters.
Takumi accepts TTF, OTF, WOFF, WOFF2, and variable fonts. Satori still needs static fonts. V7 converts static WOFF2 files or resolves static alternatives for variable fonts. Keep configured providers available if your Satori fonts need these alternatives.
Install chrome-launcher for the browser renderer
V7 no longer installs chrome-launcher. Satori and Takumi apps need no change.
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.
Restore the DevTools panel
If you use the OG Image DevTools panel, install the optional host module:
pnpm add -D nuxtseo-devtools-host@^1Restart your development server after installation. The module loads the host automatically.
You do not need to add it to modules. Production image rendering does not require it.
Check runtime cache storage
The default runtimeCacheStorage: true now uses a bounded 64 MiB memory cache when no Nitro cache mount is configured.
It evicts the least recently used images when full. Requests for evicted images render them again.
This limit covers stored image entries, rather than total server memory.
Configured Nitro cache mounts, named mounts, and explicit driver settings take precedence over this default. If you need persistence across server restarts, keep or configure a persistent driver.
Storage drivers now receive a TTL from cacheMaxAgeSeconds, with a minimum of 60 seconds.
Drivers that support expiry can remove unread entries. Explicit memory storage still has no size limit in unstorage v1.17.5.
Review custom driver limits in the Runtime Cache guide.
After upgrading, check a prerendered image, a signed runtime image, and each custom font family your templates use.