// Request-scoped schema.org nodes a page or another layer adds to the page's // one JSON-LD @graph (an ItemList on a listing, a FAQPage under a FAQ block). // useCmsSeo merges them with the CMS's nodes by @id (mergeGraph), so a page // never carries a second JSON-LD script. The list lives on the Nuxt app // instance, not in useState: it is rebuilt by the components' setup on the // server and again on the client, so it never travels in the payload. import type { MaybeRefOrGetter, ShallowRef } from 'vue' import type { SeoSchemaNode } from '../utils/types.ts' interface SchemaEntry { id: number node: MaybeRefOrGetter } type AppWithNodes = { _seoSchemaNodes?: ShallowRef; _seoSchemaNodeSeq?: number } function store(): { list: ShallowRef; app: AppWithNodes } { const app = useNuxtApp() as unknown as AppWithNodes app._seoSchemaNodes ??= shallowRef([]) return { list: app._seoSchemaNodes, app } } /** The nodes registered for the current page, resolved (refs and getters read). */ export function useSchemaNodes(): () => SeoSchemaNode[] { const { list } = store() return () => list.value.map((e) => toValue(e.node)).filter((n): n is SeoSchemaNode => !!n && typeof n === 'object') } /** * Adds `node` (a plain object, a ref or a getter; null adds nothing) to the * page's @graph for as long as the calling component lives. A node with the * @id of a CMS node is merged into it, the CMS values first. */ export function useSchemaNode(node: MaybeRefOrGetter): void { const { list, app } = store() const id = (app._seoSchemaNodeSeq = (app._seoSchemaNodeSeq ?? 0) + 1) list.value = [...list.value, { id, node }] if (import.meta.client) { onScopeDispose(() => { list.value = list.value.filter((e) => e.id !== id) }) } }