diff --git a/app/composables/useSiteMeta.js b/app/composables/useSiteMeta.js new file mode 100644 index 0000000..007a644 --- /dev/null +++ b/app/composables/useSiteMeta.js @@ -0,0 +1,58 @@ +/** + * 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 }) + } +} diff --git a/app/layouts/admin.vue b/app/layouts/admin.vue index 5e0baad..0c0d201 100644 --- a/app/layouts/admin.vue +++ b/app/layouts/admin.vue @@ -217,6 +217,8 @@ +