Add shared SEO layout and page metadata

This commit is contained in:
Calum Muir 2025-12-29 12:51:36 +00:00
parent dbb51210ab
commit d50b2971e9
13 changed files with 198 additions and 143 deletions

BIN
public/og/default.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

10
src/config/site.ts Normal file
View file

@ -0,0 +1,10 @@
export const SITE_NAME = "Highland Group RDA";
const siteUrlFromEnv =
import.meta.env.SITE ?? "https://highlandgrouprda.org.uk";
export const SITE_URL = siteUrlFromEnv;
export const DEFAULT_OG_IMAGE = "/og/default.jpg";
export const TWITTER_SITE = "@HighlandGroupRDA";
export const TWITTER_CARD = "summary_large_image";
export const OG_TYPE = "website";

View file

@ -1,91 +1,18 @@
--- ---
import "../styles/global.css"; import BaseLayout from "./BaseLayout.astro";
const { const { title, description, keywords, path, image } = Astro.props;
title = "Home",
description = "Highland Group RDA provides horse riding for people with disabilities in the Highlands, offering safe, fun, and therapeutic equestrian activities.",
keywords = "Highland Group RDA, horse riding for disabilities, therapeutic riding Highlands, RDA Scotland, equestrian therapy, disability riding lessons",
path = "/",
image = "/Samantha-and-Connolly-1280.webp",
} = Astro.props;
const SITE_NAME = "Highland Group RDA";
const BASE_URL = "https://highlandgrouprda.org.uk";
const pageTitle = `${SITE_NAME} - ${title}`;
const canonical = path === "/" ? BASE_URL : `${BASE_URL}${path}`;
const imageUrl = image.startsWith("http") ? image : `${BASE_URL}${image}`;
--- ---
<!doctype html> <BaseLayout
<html lang="en"> title={title}
<head> description={description}
<meta charset="UTF-8" /> keywords={keywords}
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> canonicalPath={path}
<title>{pageTitle}</title> ogImage={image}
<meta name="description" content={description} /> >
<meta name="keywords" content={keywords} /> <Fragment slot="head">
<link rel="canonical" href={canonical} />
<meta property="og:title" content={pageTitle} />
<meta property="og:description" content={description} />
<meta property="og:type" content="website" />
<meta property="og:url" content={canonical} />
<meta property="og:image" content={imageUrl} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={pageTitle} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={imageUrl} />
<meta name="theme-color" content="#e2e2e2" />
<link
rel="apple-touch-icon"
sizes="180x180"
href="/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="96x96"
href="/favicon-96x96.png"
/>
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="manifest" href="/site.webmanifest" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Public+Sans:wght@600;700;800&family=Merriweather:wght@700&display=swap"
rel="stylesheet"
/>
<slot name="head" /> <slot name="head" />
<script </Fragment>
src="https://cdn.userway.org/widget.js"
data-account="ZdcVJAc6ri"></script>
</head>
<body>
<div
class="snow-banner"
role="region"
aria-label="Holiday announcement"
>
<div class="snow-banner__snow" aria-hidden="true">
<span class="snowflake snowflake-1"></span>
<span class="snowflake snowflake-2"></span>
<span class="snowflake snowflake-3"></span>
<span class="snowflake snowflake-4"></span>
<span class="snowflake snowflake-5"></span>
<span class="snowflake snowflake-6"></span>
<span class="snowflake snowflake-7"></span>
<span class="snowflake snowflake-8"></span>
</div>
<a
class="snow-banner__link"
href="https://www.amazon.co.uk/hz/wishlist/ls/2B4R5YZQ8U4B2?ref_=wl_share"
target="_blank"
rel="noreferrer"
>
The ponies have had a chat and put together an Amazon Wish-List
for Christmas! Click here
</a>
</div>
<slot /> <slot />
</body> </BaseLayout>
</html>

View file

@ -0,0 +1,104 @@
---
import "../styles/global.css";
import {
DEFAULT_OG_IMAGE,
OG_TYPE,
SITE_NAME,
SITE_URL,
TWITTER_CARD,
TWITTER_SITE,
} from "../config/site";
const { title = "", description, keywords, canonicalPath, ogImage } =
Astro.props;
const pageTitle =
title && title !== SITE_NAME ? `${title} | ${SITE_NAME}` : SITE_NAME;
const canonicalUrl = new URL(
canonicalPath ?? Astro.url.pathname,
SITE_URL
).toString();
const ogImagePath = ogImage ?? DEFAULT_OG_IMAGE;
const ogImageUrl = ogImagePath
? new URL(ogImagePath, SITE_URL).toString()
: "";
const hasOgImage = Boolean(ogImagePath);
const twitterSite = TWITTER_SITE?.trim();
---
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{pageTitle}</title>
<meta name="description" content={description} />
<meta name="keywords" content={keywords} />
<link rel="canonical" href={canonicalUrl} />
<meta property="og:site_name" content={SITE_NAME} />
<meta property="og:title" content={pageTitle} />
<meta property="og:description" content={description} />
<meta property="og:type" content={OG_TYPE} />
<meta property="og:url" content={canonicalUrl} />
{hasOgImage && <meta property="og:image" content={ogImageUrl} />}
<meta name="twitter:card" content={TWITTER_CARD} />
<meta name="twitter:title" content={pageTitle} />
<meta name="twitter:description" content={description} />
{hasOgImage && <meta name="twitter:image" content={ogImageUrl} />}
{twitterSite && <meta name="twitter:site" content={twitterSite} />}
<meta name="theme-color" content="#e2e2e2" />
<link
rel="apple-touch-icon"
sizes="180x180"
href="/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="96x96"
href="/favicon-96x96.png"
/>
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="manifest" href="/site.webmanifest" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Public+Sans:wght@600;700;800&family=Merriweather:wght@700&display=swap"
rel="stylesheet"
/>
<slot name="head" />
<script
src="https://cdn.userway.org/widget.js"
data-account="ZdcVJAc6ri"></script>
</head>
<body>
<div
class="snow-banner"
role="region"
aria-label="Holiday announcement"
>
<div class="snow-banner__snow" aria-hidden="true">
<span class="snowflake snowflake-1"></span>
<span class="snowflake snowflake-2"></span>
<span class="snowflake snowflake-3"></span>
<span class="snowflake snowflake-4"></span>
<span class="snowflake snowflake-5"></span>
<span class="snowflake snowflake-6"></span>
<span class="snowflake snowflake-7"></span>
<span class="snowflake snowflake-8"></span>
</div>
<a
class="snow-banner__link"
href="https://www.amazon.co.uk/hz/wishlist/ls/2B4R5YZQ8U4B2?ref_=wl_share"
target="_blank"
rel="noreferrer"
>
The ponies have had a chat and put together an Amazon Wish-List
for Christmas! Click here
</a>
</div>
<slot />
</body>
</html>

View file

@ -2,29 +2,43 @@
import Base from "../layouts/Base.astro"; import Base from "../layouts/Base.astro";
--- ---
<Base title="Page not found" path="/404"> <Base
<div class="min-h-screen w-full flex items-center justify-center bg-[#e2e2e2] px-4"> title="Page not found"
<div class="w-full max-w-md rounded-lg border border-black/10 bg-white/90 px-6 py-6 shadow-sm"> path="/404"
description="The page you are looking for could not be found on the Highland Group RDA website."
keywords="Highland Group RDA, 404, page not found, missing page"
>
<div
class="min-h-screen w-full flex items-center justify-center bg-[#e2e2e2] px-4"
>
<div
class="w-full max-w-md rounded-lg border border-black/10 bg-white/90 px-6 py-6 shadow-sm"
>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<span class="inline-flex h-10 w-10 items-center justify-center rounded-full bg-red-100 text-red-500"> <span
class="inline-flex h-10 w-10 items-center justify-center rounded-full bg-red-100 text-red-500"
>
<svg viewBox="0 0 24 24" class="h-6 w-6" aria-hidden="true"> <svg viewBox="0 0 24 24" class="h-6 w-6" aria-hidden="true">
<path <path fill="currentColor" d="M11 7h2v6h-2zm0 8h2v2h-2z"
fill="currentColor" ></path>
d="M11 7h2v6h-2zm0 8h2v2h-2z"
/>
<path <path
fill="currentColor" fill="currentColor"
d="M12 2 1 21h22z" d="M12 2 1 21h22z"
opacity="0.15" opacity="0.15"></path>
/>
</svg> </svg>
</span> </span>
<h1 class="text-2xl font-bold text-gray-900">404 Page Not Found</h1> <h1 class="text-2xl font-bold text-gray-900">
404 Page Not Found
</h1>
</div> </div>
<p class="mt-4 text-sm text-gray-600"> <p class="mt-4 text-sm text-gray-600">
The page you are looking for does not exist. Try heading back to the homepage. The page you are looking for does not exist. Try heading back to
the homepage.
</p> </p>
<a class="mt-6 inline-flex text-sm font-semibold text-black underline" href="/">Return home</a> <a
class="mt-6 inline-flex text-sm font-semibold text-black underline"
href="/">Return home</a
>
</div> </div>
</div> </div>
</Base> </Base>

View file

@ -1,20 +1,20 @@
--- ---
import Base from "../layouts/Base.astro"; import BaseLayout from "../layouts/BaseLayout.astro";
import SiteHeader from "../components/SiteHeader.astro"; import SiteHeader from "../components/SiteHeader.astro";
import SiteFooter from "../components/SiteFooter.astro"; import SiteFooter from "../components/SiteFooter.astro";
import { navigationItems } from "../lib/navigation"; import { navigationItems } from "../lib/navigation";
const pageSeo = { const pageSeo = {
title: "About", title: "About",
path: "/about",
description: description:
"Highland Group RDA is a volunteer-led charity offering therapeutic horse riding in the Highlands. Learn about our mission, history, and community impact.", "Highland Group RDA is a volunteer-led charity offering therapeutic horse riding in the Highlands. Learn about our mission, history, and community impact.",
keywords: keywords:
"Highland Group RDA, about Highland RDA, RDA charity, volunteer-led charity Scotland, therapeutic riding, equestrian therapy Highlands", "Highland Group RDA, about Highland RDA, RDA charity, volunteer-led charity Scotland, therapeutic riding, equestrian therapy Highlands",
canonicalPath: "/about",
}; };
--- ---
<Base {...pageSeo}> <BaseLayout {...pageSeo}>
<div class="flex flex-col min-h-screen items-center bg-[#e2e2e2]"> <div class="flex flex-col min-h-screen items-center bg-[#e2e2e2]">
<main class="w-full"> <main class="w-full">
<section class="w-full max-w-[1200px] mx-auto px-0 sm:px-8 mt-0"> <section class="w-full max-w-[1200px] mx-auto px-0 sm:px-8 mt-0">
@ -380,4 +380,4 @@ const pageSeo = {
<SiteFooter fullBleedOnMobile /> <SiteFooter fullBleedOnMobile />
</main> </main>
</div> </div>
</Base> </BaseLayout>

View file

@ -1,20 +1,20 @@
--- ---
import Base from "../layouts/Base.astro"; import BaseLayout from "../layouts/BaseLayout.astro";
import SiteHeader from "../components/SiteHeader.astro"; import SiteHeader from "../components/SiteHeader.astro";
import SiteFooter from "../components/SiteFooter.astro"; import SiteFooter from "../components/SiteFooter.astro";
import { navigationItems } from "../lib/navigation"; import { navigationItems } from "../lib/navigation";
const pageSeo = { const pageSeo = {
title: "Accessibility", title: "Accessibility",
path: "/accessibility",
description: description:
"Read Highland Group RDA's Accessibility Statement and learn how we make our website inclusive and accessible for all users.", "Read Highland Group RDA's Accessibility Statement and learn how we make our website inclusive and accessible for all users.",
keywords: keywords:
"Highland Group RDA accessibility, accessible charity website, web accessibility statement, inclusive design Scotland, RDA accessibility", "Highland Group RDA accessibility, accessible charity website, web accessibility statement, inclusive design Scotland, RDA accessibility",
canonicalPath: "/accessibility",
}; };
--- ---
<Base {...pageSeo}> <BaseLayout {...pageSeo}>
<div class="flex flex-col min-h-screen items-center bg-[#e2e2e2]"> <div class="flex flex-col min-h-screen items-center bg-[#e2e2e2]">
<main class="w-full"> <main class="w-full">
<section <section
@ -171,4 +171,4 @@ const pageSeo = {
<SiteFooter fullBleedOnMobile /> <SiteFooter fullBleedOnMobile />
</main> </main>
</div> </div>
</Base> </BaseLayout>

View file

@ -1,22 +1,22 @@
--- ---
import Base from "../layouts/Base.astro"; import BaseLayout from "../layouts/BaseLayout.astro";
import SiteHeader from "../components/SiteHeader.astro"; import SiteHeader from "../components/SiteHeader.astro";
import SiteFooter from "../components/SiteFooter.astro"; import SiteFooter from "../components/SiteFooter.astro";
import { navigationItems } from "../lib/navigation"; import { navigationItems } from "../lib/navigation";
const pageSeo = { const pageSeo = {
title: "Contact", title: "Contact",
path: "/contact",
description: description:
"Contact Highland Group RDA for bookings, volunteering, or enquiries. We're here to help you connect with our team and activities in the Highlands.", "Contact Highland Group RDA for bookings, volunteering, or enquiries. We're here to help you connect with our team and activities in the Highlands.",
keywords: keywords:
"Highland Group RDA contact, RDA enquiries, book RDA session, volunteer at RDA, RDA Highlands contact details, disability riding information", "Highland Group RDA contact, RDA enquiries, book RDA session, volunteer at RDA, RDA Highlands contact details, disability riding information",
canonicalPath: "/contact",
}; };
const turnstileSiteKey = import.meta.env.PUBLIC_TURNSTILE_SITE_KEY ?? ""; const turnstileSiteKey = import.meta.env.PUBLIC_TURNSTILE_SITE_KEY ?? "";
--- ---
<Base {...pageSeo}> <BaseLayout {...pageSeo}>
{ {
turnstileSiteKey && ( turnstileSiteKey && (
<Fragment slot="head"> <Fragment slot="head">
@ -223,4 +223,4 @@ const turnstileSiteKey = import.meta.env.PUBLIC_TURNSTILE_SITE_KEY ?? "";
<SiteFooter fullBleedOnMobile /> <SiteFooter fullBleedOnMobile />
</main> </main>
</div> </div>
</Base> </BaseLayout>

View file

@ -1,5 +1,5 @@
--- ---
import Base from "../../layouts/Base.astro"; import BaseLayout from "../../layouts/BaseLayout.astro";
import SiteHeader from "../../components/SiteHeader.astro"; import SiteHeader from "../../components/SiteHeader.astro";
import SiteFooter from "../../components/SiteFooter.astro"; import SiteFooter from "../../components/SiteFooter.astro";
import { navigationItems } from "../../lib/navigation"; import { navigationItems } from "../../lib/navigation";
@ -7,15 +7,15 @@ import { newsArticles, formatNewsDate } from "../../lib/news";
const pageSeo = { const pageSeo = {
title: "News", title: "News",
path: "/events",
description: description:
"Stay updated with Highland Group RDA news, events, and stories. Discover how our riders, volunteers, and community are making a difference every day.", "Stay updated with Highland Group RDA news, events, and stories. Discover how our riders, volunteers, and community are making a difference every day.",
keywords: keywords:
"Highland RDA news, RDA events, disability riding stories, RDA updates, equestrian therapy news, Highlands charity events", "Highland RDA news, RDA events, disability riding stories, RDA updates, equestrian therapy news, Highlands charity events",
canonicalPath: "/events",
}; };
--- ---
<Base {...pageSeo}> <BaseLayout {...pageSeo}>
<div class="flex flex-col min-h-screen items-center bg-[#e2e2e2]"> <div class="flex flex-col min-h-screen items-center bg-[#e2e2e2]">
<main class="w-full"> <main class="w-full">
<section class="w-full max-w-[1200px] mx-auto px-0 sm:px-8 mt-0"> <section class="w-full max-w-[1200px] mx-auto px-0 sm:px-8 mt-0">
@ -113,4 +113,4 @@ const pageSeo = {
<SiteFooter fullBleedOnMobile /> <SiteFooter fullBleedOnMobile />
</main> </main>
</div> </div>
</Base> </BaseLayout>

View file

@ -1,5 +1,5 @@
--- ---
import Base from "../layouts/Base.astro"; import BaseLayout from "../layouts/BaseLayout.astro";
import SiteHeader from "../components/SiteHeader.astro"; import SiteHeader from "../components/SiteHeader.astro";
import SiteFooter from "../components/SiteFooter.astro"; import SiteFooter from "../components/SiteFooter.astro";
import { navigationItems } from "../lib/navigation"; import { navigationItems } from "../lib/navigation";
@ -11,15 +11,15 @@ const facebookEmbedUrl =
const pageSeo = { const pageSeo = {
title: "Facebook", title: "Facebook",
path: "/facebook",
description: description:
"Follow Highland Group RDA on Facebook to see the latest updates, photos, events, and stories from our riders, volunteers, and community.", "Follow Highland Group RDA on Facebook to see the latest updates, photos, events, and stories from our riders, volunteers, and community.",
keywords: keywords:
"Highland Group RDA Facebook, RDA social media, Highland RDA updates, RDA events Facebook, therapeutic riding community", "Highland Group RDA Facebook, RDA social media, Highland RDA updates, RDA events Facebook, therapeutic riding community",
canonicalPath: "/facebook",
}; };
--- ---
<Base {...pageSeo}> <BaseLayout {...pageSeo}>
<div class="flex flex-col min-h-screen items-center bg-[#e2e2e2]"> <div class="flex flex-col min-h-screen items-center bg-[#e2e2e2]">
<main class="w-full"> <main class="w-full">
<section class="w-full max-w-[1200px] mx-auto px-0 sm:px-8 mt-0"> <section class="w-full max-w-[1200px] mx-auto px-0 sm:px-8 mt-0">
@ -101,4 +101,4 @@ const pageSeo = {
<SiteFooter fullBleedOnMobile /> <SiteFooter fullBleedOnMobile />
</main> </main>
</div> </div>
</Base> </BaseLayout>

View file

@ -1,16 +1,16 @@
--- ---
import Base from "../layouts/Base.astro"; import BaseLayout from "../layouts/BaseLayout.astro";
import SiteHeader from "../components/SiteHeader.astro"; import SiteHeader from "../components/SiteHeader.astro";
import SiteFooter from "../components/SiteFooter.astro"; import SiteFooter from "../components/SiteFooter.astro";
import { navigationItems } from "../lib/navigation"; import { navigationItems } from "../lib/navigation";
const pageSeo = { const pageSeo = {
title: "Home", title: "Highland Group RDA",
path: "/",
description: description:
"Highland Group RDA provides horse riding for people with disabilities in the Highlands, offering safe, fun, and therapeutic equestrian activities.", "Highland Group RDA provides horse riding for people with disabilities in the Highlands, offering safe, fun, and therapeutic equestrian activities.",
keywords: keywords:
"Highland Group RDA, horse riding for disabilities, therapeutic riding Highlands, RDA Scotland, equestrian therapy, disability riding lessons", "Highland Group RDA, horse riding for disabilities, therapeutic riding Highlands, RDA Scotland, equestrian therapy, disability riding lessons",
canonicalPath: "/",
}; };
const cardData = [ const cardData = [
@ -35,7 +35,7 @@ const cardData = [
]; ];
--- ---
<Base {...pageSeo}> <BaseLayout {...pageSeo}>
<Fragment slot="head"> <Fragment slot="head">
<link <link
rel="preload" rel="preload"
@ -222,4 +222,4 @@ const cardData = [
<SiteFooter fullBleedOnMobile /> <SiteFooter fullBleedOnMobile />
</main> </main>
</div> </div>
</Base> </BaseLayout>

View file

@ -1,20 +1,20 @@
--- ---
import Base from "../layouts/Base.astro"; import BaseLayout from "../layouts/BaseLayout.astro";
import SiteHeader from "../components/SiteHeader.astro"; import SiteHeader from "../components/SiteHeader.astro";
import SiteFooter from "../components/SiteFooter.astro"; import SiteFooter from "../components/SiteFooter.astro";
import { navigationItems } from "../lib/navigation"; import { navigationItems } from "../lib/navigation";
const pageSeo = { const pageSeo = {
title: "Privacy Policy", title: "Privacy Policy",
path: "/privacy",
description: description:
"Read Highland Group RDA's Privacy Policy to learn how we collect, use, and protect your personal information in line with data protection laws.", "Read Highland Group RDA's Privacy Policy to learn how we collect, use, and protect your personal information in line with data protection laws.",
keywords: keywords:
"Highland Group RDA privacy policy, data protection RDA, GDPR compliance Scotland, RDA data use, protect personal information", "Highland Group RDA privacy policy, data protection RDA, GDPR compliance Scotland, RDA data use, protect personal information",
canonicalPath: "/privacy",
}; };
--- ---
<Base {...pageSeo}> <BaseLayout {...pageSeo}>
<div class="flex flex-col min-h-screen items-center bg-[#e2e2e2]"> <div class="flex flex-col min-h-screen items-center bg-[#e2e2e2]">
<main class="w-full"> <main class="w-full">
<section <section
@ -351,4 +351,4 @@ const pageSeo = {
<SiteFooter fullBleedOnMobile /> <SiteFooter fullBleedOnMobile />
</main> </main>
</div> </div>
</Base> </BaseLayout>

View file

@ -1,20 +1,20 @@
--- ---
import Base from "../layouts/Base.astro"; import BaseLayout from "../layouts/BaseLayout.astro";
import SiteHeader from "../components/SiteHeader.astro"; import SiteHeader from "../components/SiteHeader.astro";
import SiteFooter from "../components/SiteFooter.astro"; import SiteFooter from "../components/SiteFooter.astro";
import { navigationItems } from "../lib/navigation"; import { navigationItems } from "../lib/navigation";
const pageSeo = { const pageSeo = {
title: "Support Us", title: "Support Us",
path: "/support-us",
description: description:
"Support Highland Group RDA through donations, volunteering, or fundraising. Help us provide free, life-changing horse riding sessions in the Highlands.", "Support Highland Group RDA through donations, volunteering, or fundraising. Help us provide free, life-changing horse riding sessions in the Highlands.",
keywords: keywords:
"Support Highland RDA, donate to RDA, RDA fundraising, volunteer with RDA, charity donations Scotland, equestrian therapy support", "Support Highland RDA, donate to RDA, RDA fundraising, volunteer with RDA, charity donations Scotland, equestrian therapy support",
canonicalPath: "/support-us",
}; };
--- ---
<Base {...pageSeo}> <BaseLayout {...pageSeo}>
<div class="flex flex-col min-h-screen items-center bg-[#e2e2e2]"> <div class="flex flex-col min-h-screen items-center bg-[#e2e2e2]">
<main class="w-full"> <main class="w-full">
<section class="w-full max-w-[1200px] mx-auto px-0 sm:px-8 mt-0"> <section class="w-full max-w-[1200px] mx-auto px-0 sm:px-8 mt-0">
@ -356,4 +356,4 @@ const pageSeo = {
<SiteFooter fullBleedOnMobile /> <SiteFooter fullBleedOnMobile />
</main> </main>
</div> </div>
</Base> </BaseLayout>