/** * useSiteMeta — set page-level SEO + social meta with site defaults baked in. * * Builds absolute URLs from runtimeConfig.public.appUrl so og:image and og:url * resolve for crawlers. Defaults og:type=website, twitter:card=summary_large_image, * og:site_name=Ghost Guild. Set noindex:true to emit robots="noindex, nofollow". * * Pass a function (or refs in fields) to keep tags reactive when content loads * asynchronously via useFetch. */ export function useSiteMeta(input) { const runtimeConfig = useRuntimeConfig() const route = useRoute() const appUrl = (runtimeConfig.public.appUrl || '').replace(/\/$/, '') const resolve = () => (typeof input === 'function' ? input() : input) || {} const buildAbsolute = (path) => { if (!path) return undefined if (/^https?:\/\//i.test(path)) return path return `${appUrl}${path.startsWith('/') ? '' : '/'}${path}` } const titleGetter = () => resolve().title || 'Ghost Guild' const descGetter = () => resolve().description || undefined const isBareTitle = () => Boolean(resolve().bareTitle) const imageGetter = () => buildAbsolute(resolve().image || '/og/default.png') const typeGetter = () => resolve().type || 'website' const robotsGetter = () => resolve().noindex ? 'noindex, nofollow' : undefined const canonicalGetter = () => buildAbsolute(route.path) useSeoMeta({ title: titleGetter, description: descGetter, ogSiteName: 'Ghost Guild', ogTitle: titleGetter, ogDescription: descGetter, ogType: typeGetter, ogUrl: canonicalGetter, ogImage: imageGetter, ogImageWidth: 1200, ogImageHeight: 630, twitterCard: 'summary_large_image', twitterTitle: titleGetter, twitterDescription: descGetter, twitterImage: imageGetter, robots: robotsGetter, }) useHead({ link: [{ rel: 'canonical', href: canonicalGetter }], }) if (isBareTitle()) { useHead({ titleTemplate: null }) } }