nuxt-og-image 3.0.0-beta.25 → 3.0.0-beta.26

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 (54) hide show
  1. package/dist/client/200.html +5 -5
  2. package/dist/client/404.html +5 -5
  3. package/dist/client/_nuxt/{IconCSS.7d585b82.js → IconCSS.070dea92.js} +1 -1
  4. package/dist/client/_nuxt/builds/latest.json +1 -1
  5. package/dist/client/_nuxt/builds/meta/25560fc6-dc20-4bc7-b89d-54f84bd60ef9.json +1 -0
  6. package/dist/client/_nuxt/{entry.553872fd.js → entry.10c5818d.js} +69 -69
  7. package/dist/client/_nuxt/entry.f47d0628.css +1 -0
  8. package/dist/client/_nuxt/{error-404.5a969881.js → error-404.a5349be3.js} +1 -1
  9. package/dist/client/_nuxt/{error-500.bd8d9f3b.js → error-500.19942455.js} +1 -1
  10. package/dist/client/index.html +5 -5
  11. package/dist/module.d.mts +4 -3
  12. package/dist/module.d.ts +4 -3
  13. package/dist/module.json +1 -1
  14. package/dist/module.mjs +37 -23
  15. package/dist/runtime/cache.d.ts +1 -1
  16. package/dist/runtime/cache.mjs +5 -4
  17. package/dist/runtime/components/OgImage/OgImage.d.ts +1 -1
  18. package/dist/runtime/components/Templates/{Official → Community}/BrandedLogo.vue +3 -0
  19. package/dist/runtime/components/Templates/Community/Nuxt.vue +6 -3
  20. package/dist/runtime/components/Templates/Community/NuxtSeo.vue +142 -0
  21. package/dist/runtime/components/Templates/{Official → Community}/SimpleBlog.vue +6 -2
  22. package/dist/runtime/components/Templates/{Official → Community}/Wave.vue +4 -0
  23. package/dist/runtime/components/Templates/{Official → Community}/WithEmoji.vue +4 -0
  24. package/dist/runtime/composables/defineOgImage.mjs +3 -3
  25. package/dist/runtime/composables/defineOgImageComponent.mjs +2 -2
  26. package/dist/runtime/core/cache/prerender.d.ts +1 -1
  27. package/dist/runtime/core/font/fetch.mjs +10 -4
  28. package/dist/runtime/core/html/applyEmojis.mjs +1 -1
  29. package/dist/runtime/core/html/devIframeTemplate.mjs +13 -5
  30. package/dist/runtime/core/options/normalise.mjs +3 -2
  31. package/dist/runtime/core/renderers/chromium/screenshot.d.ts +1 -1
  32. package/dist/runtime/core/renderers/chromium/screenshot.mjs +2 -2
  33. package/dist/runtime/core/renderers/satori/index.mjs +4 -4
  34. package/dist/runtime/core/utils/resolveRendererContext.mjs +13 -3
  35. package/dist/runtime/nitro/plugins/nuxt-content.mjs +2 -3
  36. package/dist/runtime/nitro/plugins/prerender.d.ts +1 -1
  37. package/dist/runtime/nitro/plugins/prerender.mjs +12 -4
  38. package/dist/runtime/nuxt/plugins/og-image-canonical-urls.server.mjs +31 -0
  39. package/dist/runtime/nuxt/plugins/route-rule-og-image.server.mjs +5 -6
  40. package/dist/runtime/nuxt/utils.mjs +8 -10
  41. package/dist/runtime/server/routes/__og-image__/debug.json.d.ts +1 -1
  42. package/dist/runtime/server/routes/__og-image__/debug.json.mjs +3 -2
  43. package/dist/runtime/server/routes/__og-image__/image.mjs +3 -2
  44. package/dist/runtime/types.d.ts +18 -2
  45. package/dist/runtime/utils.d.ts +3 -2
  46. package/dist/runtime/utils.mjs +4 -4
  47. package/dist/runtime/utils.pure.d.ts +5 -0
  48. package/dist/runtime/utils.pure.mjs +43 -0
  49. package/package.json +1 -1
  50. package/dist/client/_nuxt/builds/meta/4219a0b1-e5b0-44b6-bf23-fa336afbfc0a.json +0 -1
  51. package/dist/client/_nuxt/entry.3a009184.css +0 -1
  52. package/dist/runtime/components/Templates/Official/Fallback.vue +0 -147
  53. package/dist/runtime/nuxt/plugins/nuxt-content-canonical-urls.mjs +0 -29
  54. /package/dist/runtime/nuxt/plugins/{nuxt-content-canonical-urls.d.ts → og-image-canonical-urls.server.d.ts} +0 -0
@@ -0,0 +1,142 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * @credits Nuxt SEO <https://nuxtseo.com/>
4
+ */
5
+
6
+ import { computed, defineComponent, h, resolveComponent } from 'vue'
7
+ import { useOgImageRuntimeConfig } from '../../../utils'
8
+ import { useSiteConfig } from '#imports'
9
+
10
+ // inherited attrs can mess up the satori parser
11
+ defineOptions({
12
+ inheritAttrs: false,
13
+ })
14
+
15
+ // convert to typescript props
16
+ const props = withDefaults(defineProps<{
17
+ colorMode?: 'dark' | 'light'
18
+ title?: string
19
+ description?: string
20
+ icon?: string | boolean
21
+ siteName?: string
22
+ siteLogo?: string
23
+ theme?: string
24
+ }>(), {
25
+ theme: '#00dc82',
26
+ title: 'title',
27
+ })
28
+
29
+ const HexRegex = /^#([0-9a-f]{3}){1,2}$/i
30
+
31
+ const runtimeConfig = useOgImageRuntimeConfig()
32
+
33
+ const colorMode = computed(() => {
34
+ return props.colorMode || runtimeConfig.colorPreference || 'light'
35
+ })
36
+
37
+ const themeHex = computed(() => {
38
+ // regex test if valid hex
39
+ if (HexRegex.test(props.theme))
40
+ return props.theme
41
+
42
+ // if it's hex without the hash, just add the hash
43
+ if (HexRegex.test(`#${props.theme}`))
44
+ return `#${props.theme}`
45
+
46
+ // if it's rgb or rgba, we convert it to hex
47
+ if (props.theme.startsWith('rgb')) {
48
+ const rgb = props.theme
49
+ .replace('rgb(', '')
50
+ .replace('rgba(', '')
51
+ .replace(')', '')
52
+ .split(',')
53
+ .map(v => Number.parseInt(v.trim(), 10))
54
+ const hex = rgb
55
+ .map((v) => {
56
+ const hex = v.toString(16)
57
+ return hex.length === 1 ? `0${hex}` : hex
58
+ })
59
+ .join('')
60
+ return `#${hex}`
61
+ }
62
+ return '#FFFFFF'
63
+ })
64
+
65
+ const themeRgb = computed(() => {
66
+ // we want to convert it so it's just `<red>, <green>, <blue>` (255, 255, 255)
67
+ return themeHex.value
68
+ .replace('#', '')
69
+ .match(/.{1,2}/g)
70
+ ?.map(v => Number.parseInt(v, 16))
71
+ .join(', ')
72
+ })
73
+
74
+ const siteConfig = useSiteConfig()
75
+ const siteName = computed(() => {
76
+ return props.siteName || siteConfig.name
77
+ })
78
+ const siteLogo = computed(() => {
79
+ return props.siteLogo || siteConfig.logo
80
+ })
81
+
82
+ const IconComponent = runtimeConfig.hasNuxtIcon
83
+ ? resolveComponent('Icon')
84
+ : defineComponent({
85
+ render() {
86
+ return h('div', 'missing nuxt-icon')
87
+ },
88
+ })
89
+ if (typeof props.icon === 'string' && !runtimeConfig.hasNuxtIcon && process.dev) {
90
+ console.warn('Please install `nuxt-icon` to use icons with the fallback OG Image component.')
91
+ // eslint-disable-next-line no-console
92
+ console.log('\nnpm add -D nuxt-icon\n')
93
+ // create simple div renderer component
94
+ }
95
+ </script>
96
+
97
+ <template>
98
+ <div
99
+ class="w-full h-full flex justify-between relative p-[60px]"
100
+ :class="[
101
+ colorMode === 'light' ? ['bg-white', 'text-gray-900'] : ['bg-gray-900', 'text-gray-50'],
102
+ ]"
103
+ >
104
+ <div
105
+ class="flex absolute top-0 right-[-100%]" :style="{
106
+ width: '200%',
107
+ height: '200%',
108
+ backgroundImage: `radial-gradient(circle, rgba(${themeRgb}, 0.5) 0%, ${colorMode === 'dark' ? 'rgba(5, 5, 5,0.3)' : 'rgba(255, 255, 255, 0.7)'} 50%, ${props.colorMode === 'dark' ? 'rgba(5, 5, 5,0)' : 'rgba(255, 255, 255, 0)'} 70%)`,
109
+ }"
110
+ />
111
+ <div class="h-full w-full justify-between relative">
112
+ <div class="flex flex-row justify-between items-center">
113
+ <div class="flex flex-col w-full" :style="icon ? { width: '65%' } : {}">
114
+ <h1 class="font-bold mb-[30px] text-[75px] max-w-[70%]">
115
+ {{ title }}
116
+ </h1>
117
+ <p
118
+ v-if="description" class="text-[35px]" :class="[
119
+ colorMode === 'light' ? ['text-gray-700'] : ['text-gray-300'],
120
+ ]"
121
+ >
122
+ {{ description }}
123
+ </p>
124
+ </div>
125
+ <div v-if="icon" style="width: 30%;" class="flex justify-end">
126
+ <IconComponent :name="icon" size="250px" style="margin: 0 auto 0 100px;opacity: 0.9;" />
127
+ </div>
128
+ </div>
129
+ <div class="flex flex-row justify-center items-center text-left w-full">
130
+ <img v-if="siteLogo" :src="siteLogo" height="30">
131
+ <template v-else>
132
+ <svg height="50" width="50" class="mr-3" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
133
+ <path :fill="theme.includes('#') ? theme : `#${theme}`" d="M62.3,-53.9C74.4,-34.5,73.5,-9,67.1,13.8C60.6,36.5,48.7,56.5,30.7,66.1C12.7,75.7,-11.4,74.8,-31.6,65.2C-51.8,55.7,-67.9,37.4,-73.8,15.7C-79.6,-6,-75.1,-31.2,-61.1,-51C-47.1,-70.9,-23.6,-85.4,0.8,-86C25.1,-86.7,50.2,-73.4,62.3,-53.9Z" transform="translate(100 100)" />
134
+ </svg>
135
+ <p v-if="siteName" style="font-size: 25px;" class="font-bold">
136
+ {{ siteName }}
137
+ </p>
138
+ </template>
139
+ </div>
140
+ </div>
141
+ </div>
142
+ </template>
@@ -1,4 +1,8 @@
1
1
  <script setup lang="ts">
2
+ /**
3
+ * @credits Full Stack Heroes <https://fullstackheroes.com/>
4
+ */
5
+
2
6
  import { parseURL } from 'ufo'
3
7
  import { computed, useSiteConfig } from '#imports'
4
8
 
@@ -25,9 +29,9 @@ const website = computed(() => {
25
29
  <h1 class="text-[80px] p-20 font-black text-left">
26
30
  {{ title }}
27
31
  </h1>
28
- <div class="text-2xl pb-10 px-20 font-bold mb-0">
32
+ <p class="text-2xl pb-10 px-20 font-bold mb-0">
29
33
  {{ website }}
30
- </div>
34
+ </p>
31
35
  </div>
32
36
  </div>
33
37
  </div>
@@ -1,4 +1,8 @@
1
1
  <script setup lang="ts">
2
+ /**
3
+ * @credits Full Stack Heroes <https://fullstackheroes.com/>
4
+ */
5
+
2
6
  // inherited attrs can mess up the satori parser
3
7
  defineOptions({ inheritAttrs: false })
4
8
 
@@ -1,4 +1,8 @@
1
1
  <script setup lang="ts">
2
+ /**
3
+ * @credits Full Stack Heroes <https://fullstackheroes.com/>
4
+ */
5
+
2
6
  // inherited attrs can mess up the satori parser
3
7
  defineOptions({ inheritAttrs: false })
4
8
 
@@ -2,7 +2,7 @@ import { defu } from "defu";
2
2
  import { appendHeader } from "h3";
3
3
  import { createRouter as createRadixRouter, toRouteMatcher } from "radix3";
4
4
  import { withoutBase } from "ufo";
5
- import { getOgImagePath } from "../utils.mjs";
5
+ import { getOgImagePath, separateProps, useOgImageRuntimeConfig } from "../utils.mjs";
6
6
  import { normaliseOptions } from "../core/options/normalise.mjs";
7
7
  import { createOgImageMeta } from "../nuxt/utils.mjs";
8
8
  import { useNuxtApp, useRequestEvent, useRouter, useRuntimeConfig } from "#imports";
@@ -28,8 +28,8 @@ export function defineOgImage(_options = {}) {
28
28
  const options = normaliseOptions({
29
29
  ..._options
30
30
  });
31
- const { defaults } = useRuntimeConfig()["nuxt-og-image"];
32
- const resolvedOptions = normaliseOptions(defu(options, routeRules, defaults));
31
+ const { defaults } = useOgImageRuntimeConfig();
32
+ const resolvedOptions = normaliseOptions(defu(separateProps(options), separateProps(routeRules), defaults));
33
33
  if (_options.url) {
34
34
  createOgImageMeta(null, options, resolvedOptions, nuxtApp.ssrContext);
35
35
  } else {
@@ -1,8 +1,8 @@
1
1
  import { defineOgImage } from "./defineOgImage.mjs";
2
2
  export function defineOgImageComponent(component, props, options = {}) {
3
3
  return defineOgImage({
4
+ ...options,
4
5
  component,
5
- props,
6
- ...options
6
+ props
7
7
  });
8
8
  }
@@ -1,6 +1,6 @@
1
1
  import type { Browser } from 'playwright-core';
2
2
  import type { OgImageOptions } from '../../types';
3
- export declare const prerenderCache: import("unstorage/dist/shared/unstorage.745f9650").a<OgImageOptions<"Fallback">> | undefined;
3
+ export declare const prerenderCache: import("unstorage/dist/shared/unstorage.745f9650").a<OgImageOptions<"NuxtSeo">> | undefined;
4
4
  export declare const prerenderChromiumContext: {
5
5
  browser?: Browser;
6
6
  };
@@ -1,4 +1,5 @@
1
1
  import { Buffer } from "node:buffer";
2
+ import { createError } from "h3";
2
3
  import { base64ToArrayBuffer } from "../env/assets.mjs";
3
4
  import { fontCache } from "./cache.mjs";
4
5
  import { useNitroOrigin, useStorage } from "#imports";
@@ -13,10 +14,15 @@ export async function loadFont({ e }, font) {
13
14
  data = base64ToArrayBuffer(await useStorage().getItem(storageKey));
14
15
  if (!data) {
15
16
  if (font.path) {
16
- data = await e.$fetch(font.path, {
17
- baseURL: useNitroOrigin(e),
18
- responseType: "arrayBuffer"
19
- });
17
+ if (import.meta.prerender) {
18
+ const key = `root:public${font.path.replace("./", ":").replace("/", ":")}`;
19
+ data = await useStorage().getItemRaw(key);
20
+ } else {
21
+ data = await e.$fetch(font.path, {
22
+ baseURL: useNitroOrigin(e),
23
+ responseType: "arrayBuffer"
24
+ });
25
+ }
20
26
  } else {
21
27
  data = await e.$fetch(`/__og-image__/font/${name}/${weight}.ttf`, {
22
28
  responseType: "arrayBuffer"
@@ -25,7 +25,7 @@ export async function applyEmojis(ctx, island) {
25
25
  }
26
26
  if (svg)
27
27
  return `
28
- ${svg.replace("<svg ", '<svg data-emoji style="margin: 0 .05em 0 .3em; vertical-align: -0.1em;" ')}
28
+ ${svg.replace("<svg ", '<svg data-emoji style="margin: 0 .05em 0 .15em; vertical-align: -0.1em;" ')}
29
29
  `;
30
30
  return match;
31
31
  }
@@ -1,11 +1,11 @@
1
1
  import { createHeadCore } from "@unhead/vue";
2
2
  import { renderSSRHead } from "@unhead/ssr";
3
+ import { useOgImageRuntimeConfig } from "../../utils.mjs";
3
4
  import { applyEmojis } from "./applyEmojis.mjs";
4
5
  import { fetchIsland } from "./fetchIsland.mjs";
5
- import { useRuntimeConfig } from "#imports";
6
6
  export async function devIframeTemplate(ctx) {
7
7
  const { options } = ctx;
8
- const { fonts, satoriOptions } = useRuntimeConfig()["nuxt-og-image"];
8
+ const { fonts, satoriOptions } = useOgImageRuntimeConfig();
9
9
  const island = await fetchIsland(ctx);
10
10
  const head = createHeadCore();
11
11
  head.push(island.head);
@@ -29,15 +29,23 @@ export async function devIframeTemplate(ctx) {
29
29
  },
30
30
  {
31
31
  innerHTML: `body {
32
- transform: scale(${options.scale || 1});
32
+ transform: scale(${options.props.scale || 1});
33
33
  transform-origin: top left;
34
34
  max-height: 100vh;
35
35
  position: relative;
36
36
  width: ${options.width}px;
37
37
  height: ${options.height}px;
38
38
  overflow: hidden;
39
- background-color: ${options.mode === "dark" ? "#1b1b1b" : "#fff"};
40
- }`
39
+ background-color: ${options.props.colorMode === "dark" ? "#1b1b1b" : "#fff"};
40
+ }
41
+ div {
42
+ display: flex;
43
+ flex-direction: column;
44
+ }
45
+ svg[data-emoji] {
46
+ display: inline-block;
47
+ }
48
+ `
41
49
  },
42
50
  ...fonts.filter((font) => font.path).map((font) => {
43
51
  return `
@@ -1,7 +1,8 @@
1
- import { unref, useRuntimeConfig } from "#imports";
1
+ import { useOgImageRuntimeConfig } from "../../utils.mjs";
2
+ import { unref } from "#imports";
2
3
  import { componentNames } from "#build/og-image-component-names.mjs";
3
4
  export function normaliseOptions(_options) {
4
- const { runtimeSatori } = useRuntimeConfig()["nuxt-og-image"];
5
+ const { runtimeSatori } = useOgImageRuntimeConfig();
5
6
  const options = { ...unref(_options) };
6
7
  if (options.static)
7
8
  options.cache = options.cache || options.static;
@@ -2,4 +2,4 @@
2
2
  import type { Buffer } from 'node:buffer';
3
3
  import type { Browser } from 'playwright-core';
4
4
  import type { H3EventOgImageRender } from '../../../types';
5
- export declare function createScreenshot({ e, options, extension }: H3EventOgImageRender, browser: Browser): Promise<Buffer>;
5
+ export declare function createScreenshot({ basePath, e, options, extension }: H3EventOgImageRender, browser: Browser): Promise<Buffer>;
@@ -1,7 +1,7 @@
1
1
  import { joinURL, withQuery } from "ufo";
2
2
  import { useNitroOrigin } from "#imports";
3
- export async function createScreenshot({ e, options, extension }, browser) {
4
- const path = options.component === "PageScreenshot" ? options.path : joinURL("/__og-image__/image", options.path, `og.html`);
3
+ export async function createScreenshot({ basePath, e, options, extension }, browser) {
4
+ const path = options.component === "PageScreenshot" ? options.path : joinURL("/__og-image__/image", basePath, `og.html`);
5
5
  const page = await browser.newPage({
6
6
  colorScheme: options.colorScheme,
7
7
  baseURL: useNitroOrigin(e)
@@ -1,11 +1,11 @@
1
1
  import { defu } from "defu";
2
+ import { useOgImageRuntimeConfig } from "../../../utils.mjs";
2
3
  import { createVNodes } from "./vnodes.mjs";
3
4
  import { loadFonts, satoriFonts } from "./fonts.mjs";
4
5
  import { useResvg, useSatori, useSharp } from "./instances.mjs";
5
- import { useRuntimeConfig } from "#imports";
6
6
  export async function createSvg(event) {
7
7
  const { options } = event;
8
- const { fonts, satoriOptions } = useRuntimeConfig()["nuxt-og-image"];
8
+ const { fonts, satoriOptions } = useOgImageRuntimeConfig();
9
9
  const vnodes = await createVNodes(event);
10
10
  if (!satoriFonts.length)
11
11
  satoriFonts.push(...await loadFonts(event, fonts));
@@ -18,7 +18,7 @@ export async function createSvg(event) {
18
18
  }));
19
19
  }
20
20
  async function createPng(event) {
21
- const { resvgOptions } = useRuntimeConfig()["nuxt-og-image"];
21
+ const { resvgOptions } = useOgImageRuntimeConfig();
22
22
  const svg = await createSvg(event);
23
23
  const Resvg = await useResvg();
24
24
  const resvg = new Resvg(svg, defu(
@@ -29,7 +29,7 @@ async function createPng(event) {
29
29
  return pngData.asPng();
30
30
  }
31
31
  async function createJpeg(event) {
32
- const { sharpOptions } = useRuntimeConfig()["nuxt-og-image"];
32
+ const { sharpOptions } = useOgImageRuntimeConfig();
33
33
  const png = await createPng(event);
34
34
  const sharp = await useSharp();
35
35
  return sharp(png, defu(event.options.sharp, sharpOptions)).jpeg().toBuffer();
@@ -6,9 +6,10 @@ import { hash } from "ohash";
6
6
  import { fetchPathHtmlAndExtractOptions } from "../options/fetch.mjs";
7
7
  import { prerenderCache } from "../cache/prerender.mjs";
8
8
  import { useChromiumRenderer, useSatoriRenderer } from "../renderers/satori/instances.mjs";
9
+ import { separateProps, useOgImageRuntimeConfig } from "../../utils.mjs";
9
10
  import { useRuntimeConfig, useSiteConfig } from "#imports";
10
11
  export async function resolveRendererContext(e) {
11
- const runtimeConfig = useRuntimeConfig()["nuxt-og-image"];
12
+ const runtimeConfig = useOgImageRuntimeConfig();
12
13
  const path = parseURL(e.path).pathname;
13
14
  const extension = path.split(".").pop();
14
15
  if (!extension) {
@@ -26,7 +27,9 @@ export async function resolveRendererContext(e) {
26
27
  const basePath = withoutTrailingSlash(
27
28
  path.replace(`/__og-image__/image`, "").replace(`/og.${extension}`, "")
28
29
  );
29
- const queryParams = { ...getQuery(e) };
30
+ let queryParams = { ...getQuery(e) };
31
+ queryParams.props = JSON.parse(queryParams.props || "{}");
32
+ queryParams = separateProps(queryParams);
30
33
  const isDebugJsonPayload = extension === "json" && runtimeConfig.debug;
31
34
  const siteConfig = useSiteConfig(e);
32
35
  const key = [
@@ -55,7 +58,14 @@ export async function resolveRendererContext(e) {
55
58
  const routeRules = defu({}, ..._routeRulesMatcher.matchAll(
56
59
  withoutBase(basePath.split("?")[0], useRuntimeConfig().app.baseURL)
57
60
  ).reverse());
58
- options = defu(queryParams, routeRules.ogImage, options, runtimeConfig.defaults);
61
+ if (typeof routeRules.ogImage === "undefined" && !options) {
62
+ return createError({
63
+ statusCode: 400,
64
+ statusMessage: "The route is missing the Nuxt OG Image payload or route rules."
65
+ });
66
+ }
67
+ const ogImageRouteRules = separateProps(routeRules.ogImage);
68
+ options = defu(queryParams, ogImageRouteRules, options, runtimeConfig.defaults);
59
69
  if (!options) {
60
70
  return createError({
61
71
  statusCode: 404,
@@ -1,14 +1,13 @@
1
1
  import { defineNitroPlugin } from "nitropack/dist/runtime/plugin";
2
2
  import { defu } from "defu";
3
- import { getOgImagePath } from "../../utils.mjs";
4
- import { useRuntimeConfig } from "#imports";
3
+ import { getOgImagePath, useOgImageRuntimeConfig } from "../../utils.mjs";
5
4
  export default defineNitroPlugin((nitroApp) => {
6
5
  nitroApp.hooks.hook("content:file:afterParse", async (content) => {
7
6
  if (content._draft || content._extension !== "md" || content._partial || content.indexable === false || content.index === false)
8
7
  return;
9
8
  if (content.path && content.ogImage) {
10
9
  const ogImageConfig = typeof content.ogImage === "object" ? content.ogImage : {};
11
- const { defaults } = useRuntimeConfig()["nuxt-og-image"];
10
+ const { defaults } = useOgImageRuntimeConfig();
12
11
  const optionsWithDefault = defu(ogImageConfig, defaults);
13
12
  const src = getOgImagePath(content.path, optionsWithDefault);
14
13
  const payload = {
@@ -1,2 +1,2 @@
1
- declare const _default: import("nitropack/dist/runtime/plugin").NitroAppPlugin;
1
+ declare const _default: import("nitropack").NitroAppPlugin;
2
2
  export default _default;
@@ -1,9 +1,11 @@
1
- import { parseURL, withoutLeadingSlash } from "ufo";
2
- import { getRouteRules } from "nitropack/dist/runtime/route-rules";
1
+ import { parseURL, withoutBase, withoutLeadingSlash } from "ufo";
3
2
  import { defineNitroPlugin } from "nitropack/dist/runtime/plugin";
3
+ import { createRouter as createRadixRouter, toRouteMatcher } from "radix3";
4
+ import { defu } from "defu";
4
5
  import { extractAndNormaliseOgImageOptions } from "../../core/options/extract.mjs";
5
6
  import { prerenderCache, prerenderChromiumContext } from "../../core/cache/prerender.mjs";
6
- import { isInternalRoute } from "../../utils.mjs";
7
+ import { isInternalRoute } from "../../utils.pure.mjs";
8
+ import { useRuntimeConfig } from "#imports";
7
9
  export default defineNitroPlugin(async (nitro) => {
8
10
  if (!import.meta.prerender)
9
11
  return;
@@ -11,7 +13,13 @@ export default defineNitroPlugin(async (nitro) => {
11
13
  const path = parseURL(e.event.path).pathname;
12
14
  if (isInternalRoute(path))
13
15
  return;
14
- const routeRules = getRouteRules(e)?.ogImage || {};
16
+ const runtimeConfig = useRuntimeConfig();
17
+ const _routeRulesMatcher = toRouteMatcher(
18
+ createRadixRouter({ routes: runtimeConfig.nitro?.routeRules })
19
+ );
20
+ const routeRules = defu({}, ..._routeRulesMatcher.matchAll(
21
+ withoutBase(path.split("?")[0], runtimeConfig.app.baseURL)
22
+ ).reverse()).ogImage;
15
23
  if (routeRules === false)
16
24
  return;
17
25
  const options = extractAndNormaliseOgImageOptions([
@@ -0,0 +1,31 @@
1
+ import { parseURL } from "ufo";
2
+ import { toValue } from "vue";
3
+ import { isInternalRoute } from "../../utils.pure.mjs";
4
+ import { defineNuxtPlugin, useRequestEvent, withSiteUrl } from "#imports";
5
+ export default defineNuxtPlugin({
6
+ setup(nuxtApp) {
7
+ nuxtApp.hooks.hook("app:rendered", async (ctx) => {
8
+ const { ssrContext } = ctx;
9
+ const e = useRequestEvent();
10
+ const path = parseURL(e.path).pathname;
11
+ if (isInternalRoute(path))
12
+ return;
13
+ ssrContext?.head.use({
14
+ key: "nuxt-og-image:canonical-urls",
15
+ hooks: {
16
+ "tags:resolve": async ({ tags }) => {
17
+ for (const tag of tags) {
18
+ if (tag.tag === "meta" && (tag.props.property === "og:image" || tag.props.name === "twitter:image:src")) {
19
+ if (!tag.props.content.startsWith("https")) {
20
+ await nuxtApp.runWithContext(() => {
21
+ tag.props.content = toValue(withSiteUrl(tag.props.content));
22
+ });
23
+ }
24
+ }
25
+ }
26
+ }
27
+ }
28
+ });
29
+ });
30
+ }
31
+ });
@@ -2,9 +2,9 @@ import { defu } from "defu";
2
2
  import { parseURL, withoutBase } from "ufo";
3
3
  import { createRouter as createRadixRouter, toRouteMatcher } from "radix3";
4
4
  import { normaliseOptions } from "../../core/options/normalise.mjs";
5
- import { getOgImagePath, isInternalRoute } from "../../utils.mjs";
5
+ import { getOgImagePath, isInternalRoute, useOgImageRuntimeConfig } from "../../utils.mjs";
6
6
  import { createOgImageMeta } from "../utils.mjs";
7
- import { defineNuxtPlugin, useRequestEvent, useRuntimeConfig } from "#imports";
7
+ import { defineNuxtPlugin, useRequestEvent } from "#imports";
8
8
  export default defineNuxtPlugin((nuxtApp) => {
9
9
  nuxtApp.hooks.hook("app:rendered", async (ctx) => {
10
10
  const { ssrContext } = ctx;
@@ -25,15 +25,14 @@ export default defineNuxtPlugin((nuxtApp) => {
25
25
  ogImageInstances?.forEach((e2) => {
26
26
  e2.dispose();
27
27
  });
28
+ nuxtApp.ssrContext._ogImagePayload = void 0;
28
29
  nuxtApp.ssrContext._ogImageInstances = void 0;
29
30
  return;
30
31
  }
31
- if (ogImageInstances.length >= 0)
32
- return;
33
32
  routeRules = defu(nuxtApp.ssrContext?.event.context._nitro?.routeRules?.ogImage, routeRules);
34
- const { defaults } = useRuntimeConfig()["nuxt-og-image"];
33
+ const { defaults } = useOgImageRuntimeConfig();
35
34
  const resolvedOptions = normaliseOptions(defu(routeRules, defaults));
36
35
  const src = getOgImagePath(ssrContext.url, resolvedOptions);
37
- createOgImageMeta(src, {}, resolvedOptions, nuxtApp.ssrContext);
36
+ createOgImageMeta(src, routeRules, resolvedOptions, nuxtApp.ssrContext);
38
37
  });
39
38
  });
@@ -1,5 +1,8 @@
1
+ import { defu } from "defu";
2
+ import { separateProps } from "../utils.mjs";
1
3
  import { useServerHead } from "#imports";
2
4
  export function createOgImageMeta(src, input, resolvedOptions, ssrContext) {
5
+ const _input = separateProps(defu(input, ssrContext._ogImagePayload));
3
6
  const url = src || input.url || resolvedOptions.url;
4
7
  let urlExtension = (url.split("/").pop() || url).split(".").pop() || resolvedOptions.extension;
5
8
  if (urlExtension === "jpg")
@@ -30,16 +33,10 @@ export function createOgImageMeta(src, input, resolvedOptions, ssrContext) {
30
33
  type: "application/json",
31
34
  processTemplateParams: true,
32
35
  innerHTML: () => {
33
- const payload = {
34
- title: "%s"
35
- };
36
- delete input.url;
37
- Object.entries({ ...input, url: void 0 }).forEach(([key, val]) => {
38
- if (typeof val !== "undefined") {
39
- payload[key.replace(/-([a-z])/g, (g) => g[1].toUpperCase())] = val;
40
- }
41
- });
42
- return payload;
36
+ if (!_input.props.title)
37
+ _input.props.title = "%s";
38
+ delete _input.url;
39
+ return _input;
43
40
  },
44
41
  // we want this to be last in our head
45
42
  tagPosition: "bodyClose"
@@ -51,5 +48,6 @@ export function createOgImageMeta(src, input, resolvedOptions, ssrContext) {
51
48
  }, {
52
49
  tagPriority: 35
53
50
  });
51
+ ssrContext._ogImagePayload = _input;
54
52
  ssrContext._ogImageInstances.push(instance);
55
53
  }
@@ -4,6 +4,6 @@ declare const _default: import("h3").EventHandler<import("h3").EventHandlerReque
4
4
  source: any;
5
5
  };
6
6
  componentNames: any;
7
- runtimeConfig: any;
7
+ runtimeConfig: import("../../../types").OgImageRuntimeConfig;
8
8
  }>>;
9
9
  export default _default;
@@ -1,9 +1,10 @@
1
1
  import { defineEventHandler, setHeader } from "h3";
2
- import { useRuntimeConfig, useSiteConfig } from "#imports";
2
+ import { useOgImageRuntimeConfig } from "../../../utils.mjs";
3
+ import { useSiteConfig } from "#imports";
3
4
  import { componentNames } from "#nuxt-og-image/component-names.mjs";
4
5
  export default defineEventHandler(async (e) => {
5
6
  setHeader(e, "Content-Type", "application/json");
6
- const runtimeConfig = useRuntimeConfig()["nuxt-og-image"];
7
+ const runtimeConfig = useOgImageRuntimeConfig();
7
8
  const siteConfig = await useSiteConfig(e, { debug: true });
8
9
  return {
9
10
  siteConfigUrl: {
@@ -4,13 +4,14 @@ import { fetchIsland } from "../../../core/html/fetchIsland.mjs";
4
4
  import { devIframeTemplate } from "../../../core/html/devIframeTemplate.mjs";
5
5
  import { applyInlineCss } from "../../../core/html/applyInlineCss.mjs";
6
6
  import { useOgImageBufferCache } from "../../../cache.mjs";
7
- import { useRuntimeConfig, useSiteConfig } from "#imports";
7
+ import { useOgImageRuntimeConfig } from "../../../utils.mjs";
8
+ import { useSiteConfig } from "#imports";
8
9
  export default defineEventHandler(async (e) => {
9
10
  const ctx = await resolveRendererContext(e);
10
11
  if (ctx instanceof H3Error)
11
12
  return ctx;
12
13
  const { isDebugJsonPayload, extension, options, renderer } = ctx;
13
- const { debug, baseCacheKey } = useRuntimeConfig()["nuxt-og-image"];
14
+ const { debug, baseCacheKey } = useOgImageRuntimeConfig();
14
15
  const compatibility = [];
15
16
  if (isDebugJsonPayload) {
16
17
  const queryExtension = getQuery(e).extension || ctx.options.extension;
@@ -3,6 +3,7 @@ import type { H3Error, H3Event } from 'h3';
3
3
  import type { ResvgRenderOptions } from '@resvg/resvg-js';
4
4
  import type { SatoriOptions } from 'satori';
5
5
  import type { AllowedComponentProps, Component, ComponentCustomProps, VNodeProps } from '@vue/runtime-core';
6
+ import type { SharpOptions } from 'sharp';
6
7
  import type { OgImageComponents } from '#nuxt-og-image/components';
7
8
  export interface H3EventOgImageRender {
8
9
  e: H3Event;
@@ -14,12 +15,26 @@ export interface H3EventOgImageRender {
14
15
  isDebugJsonPayload: boolean;
15
16
  }
16
17
  export type IconifyEmojiIconSets = 'twemoji' | 'noto' | 'fluent-emoji' | 'fluent-emoji-flat' | 'fluent-emoji-high-contrast' | 'noto-v1' | 'emojione' | 'emojione-monotone' | 'emojione-v1' | 'streamline-emojis' | 'openmoji';
18
+ export interface OgImageRuntimeConfig {
19
+ version: string;
20
+ satoriOptions: SatoriOptions;
21
+ resvgOptions: ResvgRenderOptions;
22
+ sharpOptions: SharpOptions;
23
+ runtimeSatori: boolean;
24
+ runtimeChromium: boolean;
25
+ defaults: OgImageOptions;
26
+ debug: boolean;
27
+ baseCacheKey: string;
28
+ fonts: FontConfig[];
29
+ hasNuxtIcon: boolean;
30
+ colorPreference: 'light' | 'dark';
31
+ }
17
32
  export interface OgImageComponent {
18
33
  path?: string;
19
34
  pascalName: string;
20
35
  kebabName: string;
21
36
  hash: string;
22
- category: 'app' | 'official' | 'community' | 'pro';
37
+ category: 'app' | 'community' | 'pro';
23
38
  credits?: string;
24
39
  }
25
40
  export interface ScreenshotOptions {
@@ -47,7 +62,7 @@ export type OgImagePrebuilt = {
47
62
  url: string;
48
63
  } & Pick<OgImageOptions, 'width' | 'height' | 'alt'>;
49
64
  export type DefineOgImageInput = OgImageOptions | OgImagePrebuilt | false;
50
- export interface OgImageOptions<T extends keyof OgImageComponents = 'Fallback'> {
65
+ export interface OgImageOptions<T extends keyof OgImageComponents = 'NuxtSeo'> {
51
66
  /**
52
67
  * The width of the screenshot.
53
68
  *
@@ -88,6 +103,7 @@ export interface OgImageOptions<T extends keyof OgImageComponents = 'Fallback'>
88
103
  resvg?: ResvgRenderOptions;
89
104
  satori?: SatoriOptions;
90
105
  screenshot?: Partial<ScreenshotOptions>;
106
+ sharp?: SharpOptions;
91
107
  cacheMaxAgeSeconds?: number;
92
108
  }
93
109
  export interface FontConfig {