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
|
|
@ -104,7 +104,13 @@
|
|||
</PageShell>
|
||||
</template>
|
||||
|
||||
<script setup></script>
|
||||
<script setup>
|
||||
useSiteMeta({
|
||||
title: 'About',
|
||||
description:
|
||||
'A membership community for game developers exploring cooperative models. Three circles, pay what you can. A program of Baby Ghosts, a Canadian non-profit advancing cooperative practice in the game industry since 2023.',
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* ---- ABOUT HERO ---- */
|
||||
|
|
|
|||
|
|
@ -242,6 +242,7 @@ import {
|
|||
} from "~/config/contributions";
|
||||
|
||||
definePageMeta({ layout: false });
|
||||
useSiteMeta({ title: "Accept Invitation", noindex: true });
|
||||
|
||||
const { checkMemberStatus } = useAuth();
|
||||
const { initializeHelcimPay, verifyPayment } = useHelcimPay();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
definePageMeta({ layout: false });
|
||||
useHead({ title: "Sign Out — Ghost Guild" });
|
||||
useSiteMeta({ title: "Sign Out", noindex: true });
|
||||
|
||||
// The xsrf token comes from a short-lived httpOnly cookie set by
|
||||
// oidc-provider's logoutSource callback (see server/utils/oidc-provider.ts).
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
definePageMeta({ layout: false });
|
||||
useHead({ title: "Signed Out — Ghost Guild" });
|
||||
useSiteMeta({ title: "Signed Out", noindex: true });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
definePageMeta({ layout: false });
|
||||
useHead({ title: "Sign-In Error — Ghost Guild" });
|
||||
useSiteMeta({ title: "Sign-In Error", noindex: true });
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
definePageMeta({
|
||||
layout: false,
|
||||
});
|
||||
useSiteMeta({ title: "Wiki Sign In", noindex: true });
|
||||
|
||||
const route = useRoute();
|
||||
const uid = route.query.uid as string;
|
||||
|
|
|
|||
|
|
@ -192,14 +192,10 @@ const loadTags = async () => {
|
|||
cooperativeTags.value = (data.tags || []).filter((t) => t.pool === 'cooperative')
|
||||
}
|
||||
|
||||
useHead({
|
||||
title: 'Board - Ghost Guild',
|
||||
meta: [
|
||||
{
|
||||
name: 'description',
|
||||
content: 'Share what you are seeking and offering with the Ghost Guild community.',
|
||||
},
|
||||
],
|
||||
useSiteMeta({
|
||||
title: 'Bulletin Board',
|
||||
description:
|
||||
'The Ghost Guild bulletin board. Members post offers and requests around shared interests and cooperative topics.',
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
|
|
|
|||
|
|
@ -273,8 +273,10 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
useHead({
|
||||
title: "Community Guidelines · Ghost Guild",
|
||||
useSiteMeta({
|
||||
title: "Community Guidelines",
|
||||
description:
|
||||
"What you're agreeing to when you join Ghost Guild — community values, member commitments, and the policies that govern participation.",
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -117,6 +117,33 @@ definePageMeta({
|
|||
layout: "default",
|
||||
});
|
||||
|
||||
const runtimeConfig = useRuntimeConfig();
|
||||
const siteUrl = (runtimeConfig.public.appUrl || "").replace(/\/$/, "");
|
||||
|
||||
useSiteMeta({
|
||||
title: "Ghost Guild",
|
||||
bareTitle: true,
|
||||
description:
|
||||
"Ghost Guild is where game developers explore cooperative models. Membership, events, and resources for people figuring it out together. Pay what you can.",
|
||||
});
|
||||
|
||||
useHead({
|
||||
script: [
|
||||
{
|
||||
type: "application/ld+json",
|
||||
innerHTML: JSON.stringify({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Organization",
|
||||
name: "Ghost Guild",
|
||||
url: siteUrl || "https://ghostguild.org",
|
||||
logo: `${siteUrl || "https://ghostguild.org"}/og/default.png`,
|
||||
description:
|
||||
"A membership community for game developers exploring cooperative models. A program of Baby Ghosts, a Canadian non-profit.",
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { data: events } = await useFetch("/api/events", {
|
||||
query: { limit: 4, upcoming: true },
|
||||
default: () => [],
|
||||
|
|
|
|||
|
|
@ -391,6 +391,12 @@ import {
|
|||
getGuidanceLabel,
|
||||
} from "~/config/contributions";
|
||||
|
||||
useSiteMeta({
|
||||
title: "Join",
|
||||
description:
|
||||
"Join Ghost Guild — a membership community for game developers exploring cooperative models. Everyone gets everything. Pay what you can, $0 to $50 per month.",
|
||||
});
|
||||
|
||||
// Auth state
|
||||
const { isAuthenticated, memberData, checkMemberStatus } = useAuth();
|
||||
|
||||
|
|
|
|||
|
|
@ -317,6 +317,8 @@
|
|||
import { CONTRIBUTION_PRESETS, getGuidanceLabel, requiresPayment } from '~/config/contributions';
|
||||
import { STATUS_LABELS } from '~/config/memberStatus';
|
||||
|
||||
useSiteMeta({ title: 'Account', noindex: true });
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth",
|
||||
});
|
||||
|
|
|
|||
|
|
@ -220,6 +220,8 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
useSiteMeta({ title: 'Dashboard', noindex: true });
|
||||
|
||||
const { memberData, checkMemberStatus } = useAuth();
|
||||
const { isActive, statusConfig, isPendingPayment, canPeerSupport } =
|
||||
useMemberStatus();
|
||||
|
|
|
|||
|
|
@ -50,6 +50,8 @@
|
|||
<script setup>
|
||||
definePageMeta({ middleware: 'auth' });
|
||||
|
||||
useSiteMeta({ title: 'Payment Setup', noindex: true });
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const toast = useToast();
|
||||
|
|
|
|||
|
|
@ -306,6 +306,8 @@ import { MEMBER_STATUSES } from "~/composables/useMemberStatus";
|
|||
import { TIMEZONE_OPTIONS } from "~/config/timezones";
|
||||
import { formatActivity } from "~/utils/activityText";
|
||||
|
||||
useSiteMeta({ title: "Profile", noindex: true });
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth",
|
||||
});
|
||||
|
|
|
|||
|
|
@ -276,14 +276,10 @@ onUnmounted(() => {
|
|||
pageBreadcrumbTitle.value = "";
|
||||
});
|
||||
|
||||
// Page head
|
||||
useHead({
|
||||
title: computed(() =>
|
||||
member.value
|
||||
? `${member.value.name} — Ghost Guild`
|
||||
: "Member Profile — Ghost Guild",
|
||||
),
|
||||
});
|
||||
useSiteMeta(() => ({
|
||||
title: member.value ? member.value.name : "Member Profile",
|
||||
noindex: true,
|
||||
}));
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -277,16 +277,7 @@ onBeforeUnmount(() => {
|
|||
clearTimeout(searchTimeout)
|
||||
})
|
||||
|
||||
// ---- useHead ----
|
||||
useHead({
|
||||
title: 'Member Directory - Ghost Guild',
|
||||
meta: [
|
||||
{
|
||||
name: 'description',
|
||||
content: 'Connect with members of the Ghost Guild community - game developers, founders, and practitioners building solidarity economy studios.',
|
||||
},
|
||||
],
|
||||
})
|
||||
useSiteMeta({ title: 'Member Directory', noindex: true })
|
||||
|
||||
// ---- Init ----
|
||||
onMounted(async () => {
|
||||
|
|
|
|||
|
|
@ -39,8 +39,9 @@ if (!policy) {
|
|||
throw createError({ statusCode: 404, statusMessage: 'Policy not found', fatal: true })
|
||||
}
|
||||
|
||||
useHead({
|
||||
title: `${policy.title} · Ghost Guild`,
|
||||
useSiteMeta({
|
||||
title: policy.title,
|
||||
description: policy.description,
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -231,8 +231,10 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
useHead({
|
||||
title: 'Privacy Policy · Ghost Guild',
|
||||
useSiteMeta({
|
||||
title: 'Privacy Policy',
|
||||
description:
|
||||
'How Ghost Guild handles your data: what we collect, why we collect it, and who has access. No Google Analytics, no advertising pixels, no third-party tracking.',
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -50,8 +50,10 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
useHead({
|
||||
title: 'Refund Policy · Ghost Guild',
|
||||
useSiteMeta({
|
||||
title: 'Refund Policy',
|
||||
description:
|
||||
'How Ghost Guild handles refund requests for membership dues and event tickets. Pay-what-you-can, case-by-case, run as a non-profit program of Baby Ghosts.',
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -250,8 +250,10 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
useHead({
|
||||
title: 'Terms of Service · Ghost Guild',
|
||||
useSiteMeta({
|
||||
title: 'Terms of Service',
|
||||
description:
|
||||
'Terms of service for ghostguild.org and wiki.ghostguild.org, operated by Baby Ghosts. Covers accounts, membership, acceptable use, and what we expect from each other.',
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -117,9 +117,11 @@ const handlePurchaseSuccess = () => {
|
|||
refreshNuxtData()
|
||||
}
|
||||
|
||||
useHead(() => ({
|
||||
title: series.value ? `${series.value.title} - Event Series - Ghost Guild` : 'Event Series - Ghost Guild',
|
||||
meta: [{ name: 'description', content: series.value?.description || 'Multi-event series' }],
|
||||
useSiteMeta(() => ({
|
||||
title: series.value ? `${series.value.title} · Event Series` : 'Event Series',
|
||||
description:
|
||||
series.value?.description ||
|
||||
(series.value?.title ? `${series.value.title} — a Ghost Guild event series.` : undefined),
|
||||
}))
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -72,15 +72,10 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
useHead({
|
||||
title: "Event Series - Ghost Guild",
|
||||
meta: [
|
||||
{
|
||||
name: "description",
|
||||
content:
|
||||
"Multi-session events on cooperative topics for game developers.",
|
||||
},
|
||||
],
|
||||
useSiteMeta({
|
||||
title: "Event Series",
|
||||
description:
|
||||
"Multi-session event series on cooperative topics — from foundations courses to practitioner cohorts.",
|
||||
});
|
||||
|
||||
const { data: seriesData, pending } = await useFetch("/api/series", {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
<script setup>
|
||||
definePageMeta({ layout: false })
|
||||
useSiteMeta({ title: 'Verifying', noindex: true })
|
||||
|
||||
const state = ref('verifying')
|
||||
const errorMessage = ref('')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue