SEO Fields: How the Three-Tier Fallback Works
Every page in this template gets SEO metadata through a three-tier fallback system. This ensures every page has useful meta tags without requiring every field to be filled.
The Three Tiers
- Page-level — The
...seoFieldsspread on each singleton/collection - Site-wide — Site Settings
homepageSeodefaults - Config fallback —
siteConfig.seo.default*values
How It's Wired
In BaseLayout.astro:
const data = await getSiteData();
const siteDefaults = data.homepageSeo || {};
const seoTitle = Astro.props.seo?.title || siteDefaults.title || siteConfig.seo.defaultTitle;
const seoDescription = Astro.props.seo?.description || siteDefaults.description || siteConfig.seo.defaultDescription;
const seoOgImage = Astro.props.seo?.ogImage || siteDefaults.ogImage || siteConfig.seo.defaultOgImage;
Each tier overrides the next. A page with seoTitle set uses that. Otherwise, Site Settings' homepageSeo.defaultSeoTitle is used. If that's empty too, siteConfig.seo.defaultTitle kicks in.
Adding SEO Fields
Every page schema should spread ...seoFields:
import { seoFields } from '../seo-fields';
schema: {
title: fields.text({ label: 'Page Title' }),
body: fields.markdoc({ label: 'Content' }),
...seoFields, // seoTitle, seoDescription, ogImage, canonical, noindex
}
Setting Site-Wide Defaults
In Keystatic, go to Site Settings → Homepage SEO. Set defaults for:
- Default SEO Title — used as
og:titleand<title>fallback - Default Meta Description — used as meta description fallback
- Default Social Share Image — used as
og:imagefallback
These only apply when a specific page has no SEO values set.
The Config Fallback
@template/site-config provides hardcoded fallbacks. These should only fire if both page-level and site-wide values are missing.
Page-Level Overrides
Astro pages can pass explicit seo props to BaseLayout:
<BaseLayout seo={{ title: 'Custom Title', noindex: true }}>
This is the highest priority — it bypasses all fallback tiers.