ghostguild-org/app/composables/useSiteMeta.js
Jennie Robinson Faber 31144617d7 feat(seo): site meta composable + Open Graph image generation
Adds `useSiteMeta()` composable that wraps useSeoMeta with site defaults
(title template, canonical URL, og/twitter image, og:site_name) and
absolute-URL handling via runtimeConfig.public.appUrl.

Adds /og/events/[slug].png route that renders per-event OG images via
satori + @resvg/resvg-js, cached on disk by slug + updatedAt. Bundles
Brygada 1918 + Commit Mono fonts as server assets, ships a fallback
default.png, and patches @shuding/opentype.js via patch-package.

Converts ~25 pages from useHead to useSiteMeta and adds noindex on
private/auth/admin pages.
2026-05-21 17:50:34 +01:00

58 lines
1.9 KiB
JavaScript

/**
* 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 })
}
}