nuxt-og-image 6.10.3 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/README.md +10 -14
  2. package/dist/chunks/tw4.mjs +6 -6
  3. package/dist/chunks/uno.mjs +7 -7
  4. package/dist/cli.mjs +4 -4
  5. package/dist/devtools/components/og-image/RendererSelectModal.vue +1 -1
  6. package/dist/devtools/lib/og-image/fonts.ts +38 -0
  7. package/dist/devtools/lib/og-image/types.ts +7 -0
  8. package/dist/devtools/pages/og-image/index.vue +19 -14
  9. package/dist/module.d.mts +14 -10
  10. package/dist/module.d.ts +14 -10
  11. package/dist/module.json +2 -2
  12. package/dist/module.mjs +6 -6
  13. package/dist/runtime/app/client-utils.js +7 -4
  14. package/dist/runtime/app/components/Templates/Community/SaaS.satori.vue +1 -1
  15. package/dist/runtime/app/components/Templates/Community/SimpleBlog.satori.vue +1 -1
  16. package/dist/runtime/app/disabled/components.d.ts +1 -0
  17. package/dist/runtime/app/disabled/components.js +0 -0
  18. package/dist/runtime/app/disabled/composables/defineOgImage.d.ts +1 -0
  19. package/dist/runtime/app/disabled/composables/defineOgImage.js +1 -0
  20. package/dist/runtime/app/disabled/composables/defineOgImageComponent.d.ts +1 -0
  21. package/dist/runtime/app/disabled/composables/defineOgImageComponent.js +1 -0
  22. package/dist/runtime/app/disabled/composables/defineOgImageScreenshot.d.ts +1 -0
  23. package/dist/runtime/app/disabled/composables/defineOgImageScreenshot.js +1 -0
  24. package/dist/runtime/app/disabled/composables/mock.d.ts +1 -0
  25. package/dist/runtime/app/disabled/composables/mock.js +1 -0
  26. package/dist/runtime/app/disabled/index.d.ts +3 -0
  27. package/dist/runtime/app/disabled/index.js +3 -0
  28. package/dist/runtime/app/index.d.ts +4 -0
  29. package/dist/runtime/app/index.js +4 -0
  30. package/dist/runtime/app/utils/plugins.js +1 -1
  31. package/dist/runtime/app/utils.js +2 -2
  32. package/dist/runtime/server/index.d.ts +1 -0
  33. package/dist/runtime/server/index.js +1 -0
  34. package/dist/runtime/server/middleware/signing.d.ts +2 -0
  35. package/dist/runtime/server/middleware/signing.js +19 -0
  36. package/dist/runtime/server/nitro.d.ts +3 -3
  37. package/dist/runtime/server/nitro.js +4 -4
  38. package/dist/runtime/server/og-image/bindings/font-assets/dev-prerender.js +8 -49
  39. package/dist/runtime/server/og-image/bindings/font-assets/node.js +3 -2
  40. package/dist/runtime/server/og-image/bindings/satori/node.d.ts +1 -2
  41. package/dist/runtime/server/og-image/bindings/satori/wasm-fs.d.ts +1 -2
  42. package/dist/runtime/server/og-image/bindings/satori/wasm-fs.js +1 -1
  43. package/dist/runtime/server/og-image/bindings/satori/wasm.d.ts +1 -2
  44. package/dist/runtime/server/og-image/bindings/satori/wasm.js +1 -1
  45. package/dist/runtime/server/og-image/browser/screenshot.js +5 -3
  46. package/dist/runtime/server/og-image/cache/publish.d.ts +43 -0
  47. package/dist/runtime/server/og-image/cache/publish.js +91 -0
  48. package/dist/runtime/server/og-image/cache/published.d.ts +17 -0
  49. package/dist/runtime/server/og-image/cache/published.js +49 -0
  50. package/dist/runtime/server/og-image/context.js +1 -1
  51. package/dist/runtime/server/og-image/core/plugins/imageSrc.js +2 -5
  52. package/dist/runtime/server/og-image/core/transforms/emojis/index.d.ts +3 -2
  53. package/dist/runtime/server/og-image/core/vnodes.js +19 -1
  54. package/dist/runtime/server/og-image/font-source.d.ts +1 -0
  55. package/dist/runtime/server/og-image/font-source.js +1 -0
  56. package/dist/runtime/server/og-image/fonts.d.ts +1 -1
  57. package/dist/runtime/server/og-image/fonts.js +1 -1
  58. package/dist/runtime/server/og-image/satori/filters.d.ts +1 -0
  59. package/dist/runtime/server/og-image/satori/filters.js +14 -0
  60. package/dist/runtime/server/og-image/satori/instances.d.ts +1 -2
  61. package/dist/runtime/server/og-image/satori/plugins/classes.js +0 -7
  62. package/dist/runtime/server/og-image/satori/renderer.js +2 -1
  63. package/dist/runtime/server/og-image/satori/vnodes.js +1 -4
  64. package/dist/runtime/server/og-image/takumi/renderer.js +11 -47
  65. package/dist/runtime/server/og-image/utils/css.d.ts +0 -5
  66. package/dist/runtime/server/og-image/utils/css.js +0 -5
  67. package/dist/runtime/server/plugins/auto-eject-nitro3.d.ts +2 -0
  68. package/dist/runtime/server/plugins/auto-eject-nitro3.js +9 -0
  69. package/dist/runtime/server/plugins/auto-eject.js +3 -31
  70. package/dist/runtime/server/plugins/prerender.js +68 -2
  71. package/dist/runtime/server/plugins/publish.d.ts +2 -0
  72. package/dist/runtime/server/plugins/publish.js +44 -0
  73. package/dist/runtime/server/routes/debug.json.d.ts +10 -1
  74. package/dist/runtime/server/routes/debug.json.js +4 -4
  75. package/dist/runtime/server/routes/static-font.d.ts +2 -0
  76. package/dist/runtime/server/routes/static-font.js +22 -0
  77. package/dist/runtime/server/signing-secret.d.ts +8 -0
  78. package/dist/runtime/server/signing-secret.js +75 -0
  79. package/dist/runtime/server/util/auto-eject-lifecycle.d.ts +13 -0
  80. package/dist/runtime/server/util/auto-eject-lifecycle.js +31 -0
  81. package/dist/runtime/server/util/cache.d.ts +1 -0
  82. package/dist/runtime/server/util/cache.js +12 -6
  83. package/dist/runtime/server/util/cloudflare.d.ts +2 -2
  84. package/dist/runtime/server/util/eventHandlers.js +69 -3
  85. package/dist/runtime/server/util/fetchLocalAsset.js +2 -2
  86. package/dist/runtime/server/util/origin.d.ts +3 -0
  87. package/dist/runtime/server/util/origin.js +4 -0
  88. package/dist/runtime/server/util/publishWarnings.d.ts +6 -0
  89. package/dist/runtime/server/util/publishWarnings.js +6 -0
  90. package/dist/runtime/server/utils.d.ts +3 -3
  91. package/dist/runtime/server/utils.js +5 -10
  92. package/dist/runtime/shared/publish.d.ts +13 -0
  93. package/dist/runtime/shared/publish.js +17 -0
  94. package/dist/runtime/shared/urlEncoding.js +9 -4
  95. package/dist/runtime/types.d.ts +11 -16
  96. package/dist/shared/{nuxt-og-image.Bcegsloy.mjs → nuxt-og-image.DR_SxdDv.mjs} +1224 -1109
  97. package/package.json +60 -62
  98. package/skills/nuxt-og-image/SKILL.md +31 -14
  99. package/types/virtual.d.ts +8 -0
  100. package/dist/runtime/server/og-image/satori/plugins/emojis.d.ts +0 -2
  101. package/dist/runtime/server/og-image/satori/plugins/emojis.js +0 -112
  102. package/dist/runtime/server/og-image/satori/plugins/flex.d.ts +0 -2
  103. package/dist/runtime/server/og-image/satori/plugins/flex.js +0 -48
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-og-image",
3
3
  "type": "module",
4
- "version": "6.10.3",
4
+ "version": "7.0.0",
5
5
  "description": "Enlightened OG Image generation for Nuxt.",
6
6
  "author": {
7
7
  "website": "https://harlanzw.com",
@@ -46,25 +46,27 @@
46
46
  "types/virtual.d.ts"
47
47
  ],
48
48
  "engines": {
49
- "node": ">=18.0.0"
49
+ "node": "^22.22.3 || ^24.15.0 || >=26.0.0"
50
50
  },
51
51
  "peerDependencies": {
52
- "@resvg/resvg-js": "^2.6.0",
53
- "@resvg/resvg-wasm": "^2.6.0",
54
- "@takumi-rs/core": "^1.0.0-beta.3 || ^2.0.0",
55
- "@takumi-rs/wasm": "^1.0.0-beta.3 || ^2.0.0",
56
- "@unhead/vue": "^2.0.5 || ^3.0.0",
52
+ "@resvg/resvg-js": "^2.6.2",
53
+ "@resvg/resvg-wasm": "^2.6.2",
54
+ "@takumi-rs/core": "^2.15.0",
55
+ "@takumi-rs/wasm": "^2.15.0",
56
+ "@unhead/vue": "^3.4.2",
57
57
  "@unocss/config": "^66.10.5",
58
58
  "@unocss/core": "^66.10.5",
59
- "fontless": "^0.2.0 || ^0.4.3 || ^1.0.0",
59
+ "chrome-launcher": "^1.2.2",
60
+ "fontless": "^1.3.2",
60
61
  "nitropack": "^2.13.4",
61
- "playwright-core": "^1.50.0",
62
- "satori": ">=0.33.5",
63
- "sharp": "^0.35.4",
64
- "tailwindcss": "^4.0.0",
65
- "unifont": "^0.7.0 || ^0.8.3 || ^1.0.0",
66
- "unstorage": "^1.15.0",
67
- "zod": ">=3"
62
+ "nuxt": "^4.6.1 || ^5.0.0",
63
+ "playwright-core": "^1.64.0",
64
+ "satori": "^0.47.1",
65
+ "sharp": "^0.35.5",
66
+ "tailwindcss": "^4.3.3",
67
+ "unifont": "^1.0.3",
68
+ "unstorage": "^1.17.5",
69
+ "zod": "^4.6.6"
68
70
  },
69
71
  "peerDependenciesMeta": {
70
72
  "@resvg/resvg-js": {
@@ -85,6 +87,9 @@
85
87
  "@unocss/core": {
86
88
  "optional": true
87
89
  },
90
+ "chrome-launcher": {
91
+ "optional": true
92
+ },
88
93
  "fontless": {
89
94
  "optional": true
90
95
  },
@@ -112,30 +117,29 @@
112
117
  },
113
118
  "dependencies": {
114
119
  "@clack/prompts": "^1.8.1",
115
- "@nuxt/kit": "^4.5.2",
120
+ "@nuxt/kit": "^4.6.1",
116
121
  "@vue/compiler-sfc": "^3.5.43",
117
- "chrome-launcher": "^1.2.1",
118
122
  "consola": "^3.4.2",
119
123
  "defu": "^6.1.7",
120
124
  "devalue": "^6.0.2",
121
- "exsolve": "^1.1.1",
125
+ "exsolve": "^1.1.3",
122
126
  "fnv1a-64": "^0.1.2",
123
127
  "lightningcss": "^1.33.0",
124
- "magic-string": "^1.4.2",
128
+ "lru-cache": "^11.5.3",
129
+ "magic-string": "^1.4.3",
125
130
  "magicast": "^0.5.5",
126
131
  "mocked-exports": "^0.1.1",
127
- "nuxt-site-config": "^4.2.3",
128
- "nuxtseo-shared": "^5.3.16",
132
+ "nuxt-site-config": "^5.0.0",
133
+ "nuxtseo-shared": "^6.0.0",
129
134
  "nypm": "^0.6.10",
130
135
  "object-identity": "^0.2.3",
131
136
  "ofetch": "^1.5.1",
132
137
  "ohash": "^2.0.12",
133
- "oxc-parser": "^0.151.0",
138
+ "oxc-parser": "^0.153.0",
134
139
  "oxc-walker": "^1.1.1",
135
140
  "pathe": "^2.0.3",
136
- "pkg-types": "^2.3.3",
137
141
  "radix3": "^1.1.2",
138
- "std-env": "^4.2.0",
142
+ "std-env": "^4.3.0",
139
143
  "strip-literal": "^4.0.0",
140
144
  "tinyexec": "^1.3.1",
141
145
  "ufo": "^1.6.4",
@@ -144,23 +148,23 @@
144
148
  },
145
149
  "devDependencies": {
146
150
  "@antfu/eslint-config": "^9.5.1",
147
- "@fontsource/noto-sans-sc": "^5.3.0",
151
+ "@fontsource/noto-sans-sc": "^5.3.1",
148
152
  "@iconify-json/carbon": "^1.2.28",
149
- "@iconify-json/logos": "^1.2.14",
153
+ "@iconify-json/logos": "^1.2.15",
150
154
  "@iconify-json/noto": "^1.2.9",
151
155
  "@iconify-json/ri": "^1.2.10",
152
- "@iconify-json/tabler": "^1.2.40",
156
+ "@iconify-json/tabler": "^1.2.42",
153
157
  "@iconify/types": "^2.0.0",
154
158
  "@img/sharp-linux-x64": "^0.35.5",
155
159
  "@nuxt/content": "^3.16.1",
156
- "@nuxt/devtools": "4.0.0-beta.2",
157
- "@nuxt/devtools-kit": "4.0.0-beta.2",
158
- "@nuxt/fonts": "^0.14.0",
160
+ "@nuxt/devtools": "4.0.0-beta.4",
161
+ "@nuxt/devtools-kit": "4.0.0-beta.4",
162
+ "@nuxt/fonts": "^1.0.1",
159
163
  "@nuxt/icon": "^2.5.1",
160
164
  "@nuxt/image": "^2.1.0",
161
165
  "@nuxt/module-builder": "^1.0.3",
162
- "@nuxt/test-utils": "^4.3.2",
163
- "@nuxt/ui": "^4.11.2",
166
+ "@nuxt/test-utils": "^4.3.3",
167
+ "@nuxt/ui": "^4.11.3",
164
168
  "@nuxtjs/color-mode": "^4.0.1",
165
169
  "@nuxtjs/eslint-config-typescript": "^12.1.0",
166
170
  "@nuxtjs/i18n": "^10.6.0",
@@ -168,54 +172,50 @@
168
172
  "@resvg/resvg-js": "^2.6.2",
169
173
  "@resvg/resvg-wasm": "^2.6.2",
170
174
  "@tailwindcss/vite": "^4.3.3",
171
- "@takumi-rs/core": "^2.14.0",
172
- "@takumi-rs/core-v1": "npm:@takumi-rs/core@1.8.7",
173
- "@takumi-rs/helpers": "^2.14.0",
174
- "@takumi-rs/helpers-v1": "npm:@takumi-rs/helpers@1.8.7",
175
- "@takumi-rs/wasm": "^2.14.0",
176
- "@takumi-rs/wasm-v1": "npm:@takumi-rs/wasm@1.8.7",
175
+ "@takumi-rs/core": "^2.15.0",
176
+ "@takumi-rs/helpers": "^2.15.0",
177
+ "@takumi-rs/wasm": "^2.15.0",
177
178
  "@unocss/config": "^66.10.5",
178
179
  "@unocss/core": "^66.10.5",
179
180
  "@unocss/nuxt": "^66.10.5",
180
181
  "@unocss/preset-icons": "^66.10.5",
181
182
  "@unocss/preset-wind": "^66.10.5",
182
183
  "@unocss/preset-wind4": "^66.10.5",
183
- "@vitejs/plugin-vue": "^6.0.9",
184
+ "@vitejs/plugin-vue": "^6.0.10",
184
185
  "@vue/compiler-core": "^3.5.43",
185
186
  "@vueuse/nuxt": "^15.0.0",
187
+ "aws4fetch": "^1.0.20",
186
188
  "birpc": "^4.2.0",
187
189
  "bumpp": "^12.3.0",
188
- "eslint": "^10.11.0",
190
+ "chrome-launcher": "^1.2.2",
191
+ "eslint": "^10.12.0",
189
192
  "eslint-plugin-harlanzw": "^0.23.0",
190
- "fontless": "^1.2.1",
191
- "fontless-v0-2": "npm:fontless@0.2.0",
193
+ "fontless": "^1.3.2",
192
194
  "globby": "^16.2.4",
193
- "happy-dom": "^20.14.5",
195
+ "happy-dom": "^21.0.1",
194
196
  "hookable": "^6.1.2",
195
197
  "jest-image-snapshot": "^6.5.2",
196
198
  "lightningcss": "^1.33.0",
197
199
  "nitropack": "^2.13.4",
198
- "nuxt": "^4.5.2",
200
+ "nuxt": "4.6.1",
199
201
  "nuxtseo-layer-devtools": "^5.3.16",
200
- "playwright": "^1.63.0",
201
- "playwright-core": "^1.63.0",
202
- "sass": "^1.105.0",
203
- "satori": "^0.33.5",
202
+ "playwright": "^1.64.0",
203
+ "playwright-core": "^1.64.0",
204
+ "sass": "^1.105.1",
205
+ "satori": "^0.47.1",
204
206
  "sharp": "^0.35.5",
205
207
  "sirv": "^3.0.2",
206
208
  "tailwindcss": "^4.3.3",
207
209
  "typescript": "6.0.3",
208
210
  "unbuild": "^3.6.1",
209
- "unifont": "^1.0.2",
210
- "unifont-v0-7": "npm:unifont@0.7.0",
211
+ "unifont": "^1.0.3",
211
212
  "unocss": "^66.10.5",
212
- "vite": "8.3.0",
213
- "vitest": "^5.0.2",
213
+ "vite": "^8.3.4",
214
+ "vitest": "^5.0.3",
214
215
  "vue": "^3.5.43",
215
- "vue-tsc": "^3.3.11",
216
- "wrangler": "^4.142.0",
217
- "yoga-wasm-web": "^0.3.3",
218
- "zod": "^4.6.5"
216
+ "vue-tsc": "^3.3.13",
217
+ "wrangler": "^4.149.0",
218
+ "zod": "^4.6.6"
219
219
  },
220
220
  "scripts": {
221
221
  "bench:edge-startup": "nuxt build bench/fixtures/edge-startup && node bench/edge-startup.mjs bench/fixtures/edge-startup/.netlify/edge-functions/server/server.js",
@@ -233,13 +233,11 @@
233
233
  "release": "pnpm build && bumpp -x \"npx changelogen --output=CHANGELOG.md\"",
234
234
  "typecheck": "pnpm stub && nuxt typecheck",
235
235
  "test": "pnpm run prepare:fixtures && vitest",
236
- "test:run": "pnpm test -- --run && pnpm test:nuxt5",
237
- "test:nuxt5": "pnpm pack --out test/fixtures/nuxt5/nuxt-og-image.tgz && node -e \"require('node:fs').rmSync('test/fixtures/nuxt5/node_modules', { recursive: true, force: true })\" && pnpm install --dir test/fixtures/nuxt5 --no-frozen-lockfile --force --update-checksums && pnpm --dir test/fixtures/nuxt5 test",
236
+ "test:run": "pnpm prepare:fixtures && vitest run && pnpm test:nuxt5",
237
+ "test:nuxt5": "pnpm build && pnpm --dir test/fixtures/nuxt5 install --frozen-lockfile && pnpm --dir test/fixtures/nuxt5 test",
238
238
  "test:chrome": "HAS_CHROME=true vitest",
239
239
  "test:packed-install": "node scripts/smoke-packed-install.mjs",
240
- "test:peers": "node scripts/test-peer-compatibility.mjs",
241
- "test:takumi:e2e": "node scripts/test-takumi-e2e.mjs",
242
- "test:takumi:e2e:matrix": "node scripts/test-takumi-e2e.mjs --matrix",
243
- "test:takumi:e2e:compare": "node scripts/test-takumi-e2e.mjs --compare"
240
+ "test:peers": "node scripts/test-peer-compatibility.ts",
241
+ "test:takumi:e2e": "node scripts/test-takumi-e2e.ts"
244
242
  }
245
243
  }
@@ -1,15 +1,19 @@
1
1
  ---
2
2
  name: nuxt-og-image
3
3
  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.
4
+ license: MIT
5
+ 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."
4
6
  ---
5
7
 
6
8
  # nuxt-og-image
7
9
 
8
- Tested against `nuxt-og-image` 6.9.1 on Nuxt 4.5 with `@takumi-rs/core` 2 (requires Nuxt `>=3.16.0`).
10
+ Requires Nuxt `^4.6.1 || ^5.0.0` and Unhead 3.4.2 or newer.
9
11
  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
10
12
 
11
13
  ## Setup
12
14
 
15
+ - Included in `@nuxtjs/seo` v6. If the app uses `@nuxtjs/seo`, configure the `ogImage` key and do not add the module again.
16
+ - Otherwise, install it with `npx nuxt module add nuxt-og-image`.
13
17
  - 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.
14
18
  - 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.
15
19
  - 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`.
@@ -22,7 +26,7 @@ The module renders a Vue component to a PNG and adds `og:image` and `twitter:ima
22
26
  - `defineOgImage`, `defineOgImageScreenshot`, and `getOgImagePath` are auto-imported. `getOgImageUrl` is auto-imported in server code.
23
27
  - 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.
24
28
  - Default size is 1200 by 600, not 1200 by 630. Set `ogImage.defaults.height` to change it.
25
- - Runtime URLs are signed. With no `security.secret`, the module generates one per build and warns in dev. 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.
29
+ - 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.
26
30
  - A prerendered page gets a static file at `/_og/s/`. A server rendered page gets `/_og/d/` with all props encoded in the path.
27
31
  - Inter 400 and 700 are bundled. Emoji use the `noto` set and follow the size of the surrounding text.
28
32
 
@@ -82,16 +86,6 @@ export default defineNuxtConfig({
82
86
  Keep runtime URLs short: pass a slug, then `await $fetch()` the data inside the template.
83
87
  To use an existing image, call `useSeoMeta({ ogImage: '/cover.png' })`. The v5 `defineOgImage({ url })` form is gone.
84
88
 
85
- ## Traps
86
-
87
- - **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')` 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.
88
- - **`zeroRuntime: true` removes the `/_og/d/` handler.** A page that is not prerendered gets no `og:image`. Prerender every page that calls `defineOgImage()`.
89
- - **`defineOgImageScreenshot()` needs `ogImage.browser`.** Without it, the production build fails. Set `browser: true` for local Chrome or Playwright, or `{ provider: 'cloudflare', binding: 'BROWSER' }`. Use it for prerendered pages; most hosts cannot run a browser.
90
- - **`defineOgImage()` in a client only component throws in dev and renders nothing in production.** Call it in page or layout setup, on the server.
91
- - **A border class needs `border-solid`.** A width without a style draws nothing, as in CSS.
92
- - **Rolling or multi instance deploys need a stable secret.** Set `NUXT_OG_IMAGE_SECRET` from `pnpm exec nuxt-og-image generate-secret`. Otherwise a URL signed by one build fails on another with 403.
93
- - **A wildcard route rule with `swr`, `isr`, or `cache` breaks `/_og/` routes.** The module warns. Use narrower patterns such as `/blog/**`.
94
-
95
89
  ## Fonts
96
90
 
97
91
  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.
@@ -108,8 +102,31 @@ export default defineNuxtConfig({
108
102
  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' }`.
109
103
  Guide: https://nuxtseo.com/docs/og-image/guides/cloudflare
110
104
 
105
+ ## Traps
106
+
107
+ - **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.
108
+ - **`zeroRuntime: true` removes the `/_og/d/` handler.** A page that is not prerendered gets no `og:image`. Prerender every page that calls `defineOgImage()`.
109
+ - **`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.
110
+ - **`defineOgImage()` in a client only component throws in dev and renders nothing in production.** Call it in page or layout setup, on the server.
111
+ - **A border class needs `border-solid`.** A width without a style draws nothing, as in CSS.
112
+ - **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.
113
+ - **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.
114
+ - **A wildcard route rule with `swr`, `isr`, or `cache` breaks `/_og/` routes.** The module warns. Use narrower patterns such as `/blog/**`.
115
+
111
116
  ## Version limits
112
117
 
118
+ v7 changes that v6 examples still show:
119
+
120
+ - Nuxt versions before 4.6.1 are not supported. Upgrade Nuxt to 4.6.1 or later.
121
+ - 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.
122
+ - 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.
123
+ - Custom fonts need `@nuxt/fonts` 1.0+. With an older version, images fall back to Inter.
124
+ - Takumi v1 is removed. Use `@takumi-rs/core` or `@takumi-rs/wasm` `^2.15.0`. Merge `takumi.fetchedResources` and `takumi.persistentImages` into `takumi.images`.
125
+ - `defineOgImageSchema()` needs Zod 4.
126
+ - `getOgImageUrl(event, ...)` takes the Nuxt `RequestEvent`. Import `defineEventHandler` from `nuxt/server`, not the h3 auto-import.
127
+ - `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.
128
+ - The DevTools panel needs `nuxtseo-devtools-host` as a dev dependency: `pnpm add -D nuxtseo-devtools-host@^1`.
129
+
113
130
  v6 changes that older examples still show:
114
131
 
115
132
  - `defineOgImageComponent('X', props)` is deprecated. Use `defineOgImage('X', props)`.
@@ -123,12 +140,12 @@ The `html` option is deprecated, and `security.strict` removes it. Use a compone
123
140
 
124
141
  - `defaults`: default image options, such as `width`, `height`, `extension`, `emojis`. Options equal to a default stay out of the URL.
125
142
  - `zeroRuntime` (`false`): drop the runtime renderer. See the trap above.
126
- - `security.secret`, `security.strict` (`false`): `strict` requires an explicit secret, drops `html`, and restricts runtime images to the site host.
143
+ - `security.secret`, `security.strict` (`false`): `strict` requires Nuxt `appSecret`, drops `html`, and restricts runtime images to the site host.
127
144
  - `browser`: enables screenshots and `.browser.vue` templates.
128
145
  - Other options: https://nuxtseo.com/docs/og-image/api/config
129
146
 
130
147
  ## Debug
131
148
 
132
- - Nuxt DevTools has an OG Image tab with a live preview.
149
+ - Nuxt DevTools has an OG Image tab with a live preview. The tab needs `nuxtseo-devtools-host` as a dev dependency.
133
150
  - In dev, the image at a URL is cached. After a template edit, append `?purge` to the image URL.
134
151
  - `ogImage.debug: true` adds `/_og/debug.json` and logs more. It warns if left on in production.
@@ -1,3 +1,11 @@
1
+ declare module '#build/nuxt-og-image/client-config.mjs' {
2
+ export const hasServerRuntime: boolean
3
+ }
4
+
5
+ declare module '#og-image-virtual/signing.mjs' {
6
+ export const rendersImages: boolean
7
+ }
8
+
1
9
  declare module '#nuxtseo/h3' {
2
10
  export * from 'h3'
3
11
  }
@@ -1,2 +0,0 @@
1
- declare const _default: import("../../../../types.js").VNodeTransformer | import("../../../../types.js").VNodeTransformer[];
2
- export default _default;
@@ -1,112 +0,0 @@
1
- import { parse } from "ultrahtml";
2
- import { querySelector } from "ultrahtml/selector";
3
- import { getEmojiSvg } from "#og-image/emoji-transform";
4
- import { defineTransformer } from "../../core/plugins.js";
5
- import { RE_MATCH_EMOJIS } from "../../core/transforms/emojis/emoji-utils.js";
6
- import { elementToVNode } from "../../core/vnodes.js";
7
- function svgToVNode(svg) {
8
- const doc = parse(svg);
9
- const svgEl = querySelector(doc, "svg");
10
- if (!svgEl)
11
- return null;
12
- return elementToVNode(svgEl);
13
- }
14
- function isEmojiSvg(node) {
15
- return node.type === "svg" && typeof node.props?.["data-emoji"] !== "undefined";
16
- }
17
- export default defineTransformer([
18
- // Transform text nodes that contain emojis to replace them with SVG nodes
19
- {
20
- filter: (node) => {
21
- if (typeof node.props?.children !== "string")
22
- return false;
23
- RE_MATCH_EMOJIS.lastIndex = 0;
24
- return RE_MATCH_EMOJIS.test(node.props.children);
25
- },
26
- transform: async (node, ctx) => {
27
- const text = node.props.children;
28
- RE_MATCH_EMOJIS.lastIndex = 0;
29
- const matches = [...text.matchAll(RE_MATCH_EMOJIS)];
30
- if (!matches.length)
31
- return;
32
- const children = [];
33
- let lastIndex = 0;
34
- for (const match of matches) {
35
- const emoji = match[0];
36
- const emojiIndex = match.index;
37
- if (emojiIndex > lastIndex) {
38
- const beforeText = text.slice(lastIndex, emojiIndex);
39
- if (beforeText)
40
- children.push(beforeText);
41
- }
42
- const svg = await getEmojiSvg(ctx, emoji);
43
- if (svg) {
44
- const svgNode = svgToVNode(svg);
45
- if (svgNode) {
46
- svgNode.props["data-emoji"] = true;
47
- const existingStyle = svgNode.props.style || {};
48
- if (existingStyle.display === "inline-block")
49
- delete existingStyle.display;
50
- svgNode.props.style = {
51
- ...existingStyle,
52
- width: "1em",
53
- height: "1em"
54
- };
55
- children.push(svgNode);
56
- } else {
57
- children.push(emoji);
58
- }
59
- } else {
60
- children.push(emoji);
61
- }
62
- lastIndex = emojiIndex + emoji.length;
63
- }
64
- if (lastIndex < text.length) {
65
- const afterText = text.slice(lastIndex);
66
- if (afterText)
67
- children.push(afterText);
68
- }
69
- if (children.length >= 1) {
70
- const vnodeChildren = children.map((child) => {
71
- if (typeof child === "string") {
72
- return {
73
- type: "div",
74
- props: {
75
- children: child
76
- }
77
- };
78
- }
79
- return child;
80
- });
81
- node.props.children = vnodeChildren;
82
- node.props.style = node.props.style || {};
83
- node.props.style.display = "flex";
84
- node.props.style.alignItems = "center";
85
- }
86
- }
87
- },
88
- // Keep the existing logic for styling containers with emoji SVGs
89
- {
90
- filter: (node) => ["div", "p"].includes(node.type) && Array.isArray(node.props?.children) && node.props.children.some(
91
- (child) => child.type === "svg" && child.props?.["data-emoji"] || isEmojiSvg(child)
92
- ),
93
- transform: (node) => {
94
- node.props.style = node.props.style || {};
95
- node.props.style.display = "flex";
96
- node.props.style.alignItems = "center";
97
- node.props.children = node.props.children.map((child) => {
98
- if (typeof child === "string") {
99
- return {
100
- type: "div",
101
- props: {
102
- children: child
103
- }
104
- };
105
- }
106
- if (child.props?.class?.includes("emoji"))
107
- delete child.props.class;
108
- return child;
109
- });
110
- }
111
- }
112
- ]);
@@ -1,2 +0,0 @@
1
- declare const _default: import("../../../../types.js").VNodeTransformer | import("../../../../types.js").VNodeTransformer[];
2
- export default _default;
@@ -1,48 +0,0 @@
1
- import { defineTransformer } from "../../core/plugins.js";
2
- const RE_FLEX_CLASS = /\bflex\b/;
3
- const INLINE_ELEMENTS = [
4
- "span",
5
- "a",
6
- "b",
7
- "i",
8
- "u",
9
- "em",
10
- "strong",
11
- "code",
12
- "abbr",
13
- "del",
14
- "ins",
15
- "mark",
16
- "sub",
17
- "sup",
18
- "small",
19
- "p",
20
- "h1",
21
- "h2",
22
- "h3",
23
- "h4",
24
- "h5"
25
- ];
26
- export default defineTransformer({
27
- filter: (node) => [...INLINE_ELEMENTS, "div"].includes(node.type) && (Array.isArray(node.props?.children) && node.props?.children.length >= 1) && !node.props?.class?.includes("hidden"),
28
- transform: (node) => {
29
- node.props.style = node.props.style || {};
30
- if (node.props.style.display) {
31
- return;
32
- }
33
- if (node.type === "div") {
34
- node.props.style.display = "flex";
35
- if (!node.props.style.flexDirection && !RE_FLEX_CLASS.test(node.props?.class || "")) {
36
- node.props.style.flexDirection = "column";
37
- }
38
- return;
39
- }
40
- node.props.style.display = "flex";
41
- if (!node.props.style.flexWrap) {
42
- node.props.style.flexWrap = "wrap";
43
- }
44
- if (node.props.style.flexWrap === "wrap" && !node.props.style.gap) {
45
- node.props.style.gap = "0.2em";
46
- }
47
- }
48
- });