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.
This commit is contained in:
parent
877ef1a220
commit
31144617d7
36 changed files with 1182 additions and 53 deletions
58
app/composables/useSiteMeta.js
Normal file
58
app/composables/useSiteMeta.js
Normal file
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue