Contact Us

SEO and Analytics

DOC-00022 reference implementor, developer, editor

This reference covers the theme’s SEO metadata contracts, structured data output, sitemap and feed generation, and analytics/privacy compliance model.

Who this is for

  • Implementors configuring SEO metadata, structured data, or analytics providers
  • Developers extending the SEO component or adding new metadata outputs
  • Editors verifying that content pages meet metadata requirements

Metadata Requirements (REQ-00099)

Every page must include the following metadata:

FieldRequiredNotes
<title>YesUnique per page
<meta name="description">Yes50–160 characters
Canonical URLYesSelf-referential <link rel="canonical">
og:titleYesCan match <title>
og:descriptionYesCan match description
og:urlYesCanonical URL
og:imageYes1200×630 recommended

The SEO component in src/core/components/base/SEO.astro implements this contract. It is included automatically in BaseLayout — do not add it manually in page templates.

Pages must not ship without a populated title and description. The validation pipeline enforces this at build time via lint:content-ingestion, which checks frontmatter for required fields.

Robots: All pages are indexable by default. Set noindex: true in frontmatter to exclude a page from search engines (e.g., QA-only showcase pages). Noindex is enforced via the HTML <meta name="robots" content="noindex,nofollow"> tag.

Site-level defaults (site name, default OG image, author) are configured in src/site/config/site.ts.

Structured Data — JSON-LD (REQ-00193)

The StructuredData component in src/core/components/base/StructuredData.astro emits JSON-LD <script> blocks in the <head>. It is included automatically in BaseLayout.

Supported schema types:

SchemaSourceCondition
OrganizationsiteConfig.organizationAlways emitted when configured
LocalBusinesssiteConfig.localBusinessEmitted instead of Organization when configured (mutually exclusive)
WebPagePage metadataEmitted on indexable pages only
ArticleArticle frontmatterEmitted on article pages (type="article")
BreadcrumbListbreadcrumbs layout propEmitted when breadcrumb data is passed to the layout

The FAQSchema component (src/core/components/base/FAQSchema.astro) emits FAQPage JSON-LD inline in the body. Use it in any page or MDX content:

<FAQSchema items={[{ question: "What is this?", answer: "A test." }]} />

Open Graph (OG) Image Generation (REQ-00194)

Open Graph images (1200×630 PNG) are generated at runtime on first request for all indexable content entries and static pages. The route is src/pages/og/[...path].ts. (PLN-23)

Runtime behavior

Images are generated on-demand when first requested, then cached in memory for the container lifetime. Cache resets on restart or redeployment — no manual invalidation needed. This avoids rebuilding the full OG image set on every deploy.

  • Content collection entries (pages, articles) are discovered via getCollection (non-draft, non-noindex)
  • Static .astro pages opt in by exporting export const meta = { title: "Page Title" } from their code fence
  • Dynamic routes, docs, and 404 pages are excluded
  • First-request latency is typically under 0.5 seconds (Satori + resvg)
  • Social crawlers receive Content-Type: image/png and Cache-Control: public, max-age=31536000, immutable

Open Graph configuration (og.config.ts)

The OG template’s visual parameters are configurable via a core/site config pair:

  • Core schema: src/core/config/og.config.ts — exports OgConfig type and DEFAULT_OG_CONFIG
  • Site instance: src/site/config/og.config.ts — imports defaults, spreads, overrides
FieldTypeDefaultDescription
siteNamestring (optional)siteConfig.siteNameSite name rendered in the OG image
backgroundColorstring (hex)"#1a1a2e"Background color
titleColorstring (hex)"#e8e8e8"Page title text color
siteNameColorstring (hex)"#888888"Site name text color

When siteName is omitted, the OG route resolves it from siteConfig.siteName at runtime. Provide it when the OG image should show a different name (e.g., shorter brand name).

A site with no og.config.ts customizations produces the same OG images as the original hardcoded template — zero setup cost.

Font asset management

The OG font is a site-owned asset, separate from the Astro Fonts API display fonts. Satori requires raw font bytes (TTF/OTF), not CSS @font-face declarations.

  • Source TTF: src/site/assets/og/Inter_24pt-Regular.ttf (SITE-OWNED)
  • Generated module: src/site/assets/og/og-font-data.ts (SITE-OWNED, committed)

The font bytes are embedded as base64 in the generated TypeScript module, decoded once at module load via atob + Uint8Array.from. No filesystem access at runtime — compatible with Node, Cloudflare Workers, and Vercel Edge.

To replace the OG font:

  1. Replace src/site/assets/og/Inter_24pt-Regular.ttf with the new TTF
  2. Run the regeneration command documented in the og-font-data.ts file header
  3. Commit both the new TTF and the regenerated module

Font weight note: The template registers the same font buffer for weight 400 (site name) and weight 700 (page title). Satori synthesizes the bold weight. A variable-weight font or one with built-in bold produces better results than a single-weight file.

Per-page Open Graph image override

Override with ogImage in frontmatter to use a custom image instead of the generated one.

Open Graph image resolution chain

SEO.astro resolves OG images in this order:

  1. Explicit per-page ogImage (frontmatter/props) — always wins
  2. Generated /og/{path}/ — for eligible routes (non-noindex)
  3. siteConfig.ogImage — fallback for non-eligible routes (e.g., noindex pages)

For generated OG routes, SEO.astro also emits og:image:type, og:image:width, and og:image:height meta tags.

Sitemap (REQ-00093, REQ-00094)

Both the HTML sitemap (/sitemap/) and XML sitemap (/sitemap.xml) are prerendered page routes that use getCollection() directly. They share a unified filter: !draft && !noindex && !excludeFromSitemap, scoped to collections listed in siteConfig.sitemapCollections (default: ["pages", "articles"]).

Static .astro pages (e.g., /contact/, /search/) are discoverable via linkedRoute stubs — metadata-only .md files in src/content/pages/ whose linkedRoute field points to the actual URL. The [...slug].astro catch-all filters out linkedRoute entries to avoid route conflicts.

Pages are excluded from sitemaps when:

  • Frontmatter has draft: true
  • Frontmatter has noindex: true (industry standard — noindex pages don’t belong in sitemaps)
  • Frontmatter has excludeFromSitemap: true (escape hatch for indexed pages that shouldn’t appear)
  • The collection is not in sitemapCollections

Robots.txt (REQ-00097)

Generated at /robots.txt from siteConfig.robots. Configure in src/site/config/site.ts:

robots: {
  disallow: ["/private/"],
  allow: ["/public/"],
  additionalRules: "User-agent: Googlebot\nAllow: /",
},

RSS Feed (REQ-00183)

Generated at /rss.xml using @astrojs/rss. Includes published articles, excludes drafts and entries with excludeFromFeed: true in frontmatter. RSS autodiscovery is included in the HTML <head> on all pages.

Web Manifest (REQ-00098)

Served as a static file at public/site.webmanifest. Astro v6 introduced a conflict between trailingSlash: "always" and file-extension endpoints that made both SSR and prerendered routes unviable (D-C10-06-R1). Static files in public/ bypass Astro routing entirely.

Creating the Manifest

Edit public/site.webmanifest directly. Required fields:

FieldDescriptionExample
nameFull site name shown in install prompts"Peak Performance Digital"
short_nameAbbreviated name for home-screen icons"PkPd"
start_urlURL opened when the app launches"/"
displayDisplay mode"standalone"
background_colorSplash screen background (hex)"#ffffff"
theme_colorBrowser chrome color (hex)"#ffffff"
iconsArray of icon objects (see below)

Required Icon Files

Place these in public/:

FileSizePurpose
android-chrome-192x192.png192×192Standard app icon
android-chrome-512x512.png512×512High-res / splash

Each icon entry needs src, sizes, and type:

{
  "src": "/android-chrome-192x192.png",
  "sizes": "192x192",
  "type": "image/png"
}

Per-Site Setup

When creating a new site, update all values in public/site.webmanifest to match the site’s branding:

  1. Set name and short_name to the site’s name.
  2. Set background_color and theme_color to match the site’s palette.
  3. Generate icon PNGs at 192×192 and 512×512 and place them in public/.
  4. Verify the manifest loads at /site.webmanifest after build.

Trailing-Slash Enforcement (REQ-00145, REQ-00200)

Controlled by platformConfig.trailingSlash in src/site/config/platform.config.ts. When enabled, Astro config and middleware both use that value so non-trailing-slash URLs redirect to their canonical trailing-slash form.

Taxonomy Indexability (REQ-00193)

Configure via siteConfig.taxonomy:

taxonomy: {
  categoryIndexable: true,  // default
  tagIndexable: false,       // default
},

Use isTaxonomyIndexable("category" | "tag") from src/lib/get-taxonomy-indexability.ts in taxonomy routes to determine noindex state.

Analytics and Privacy (REQ-00100–REQ-00105)

The theme ships a CORE-OWNED consent mechanism that gates all analytics script loading behind user consent. This includes a ConsentBanner component (Accept / Reject / Required Only), consent state persistence in localStorage, and a consent-gated dynamic injection engine in Analytics.astro.

Key behaviors:

  • When consentRequired is true, no analytics scripts appear in the initial HTML — they are injected dynamically after the user accepts consent
  • Consent state is stored in localStorage under the key analytics-consent as a structured object with status and timestamp
  • The ConsentReset component provides consent revocation for placement on the privacy policy page
  • When consentRequired is false, scripts load immediately with no banner (pre-consent behavior for sites that don’t need consent gating)

For full configuration reference, consent states, script injection lifecycle, event taxonomy, and custom snippet setup, see Analytics & Consent.

REQ-00091 implemented Canonical URLs shall be generated automatically.
REQ-00092 implemented Generated URLs shall be overridable.
REQ-00093 implemented Sitemap generation shall be supported.
REQ-00094 implemented Content shall be able to exclude itself from sitemap outputs.
REQ-00097 implemented Robots.txt generation shall be supported.
REQ-00098 implemented Web Manifest support shall be provided.
REQ-00099 normative Metadata requirements shall apply to all pages.
REQ-00105 normative CCPA compliance mechanisms shall be supported.
REQ-00145 implemented In production-aligned outputs (build/preview/deployed), requests for URLs missing the required trailing slash shall automatically redirect to the trailing-slash canonical URL.
REQ-00183 implemented The system shall generate RSS and/or Atom feeds. Content shall be able to exclude itself from generated feeds.
REQ-00193 implemented The theme shall provide first-class structured data support for Organization, WebPage, Article, FAQPage, BreadcrumbList, and LocalBusiness schema types.
REQ-00194 implemented Open Graph images shall be generated using content-driven inputs (e.g., page title, description, or configured template) with content-level overrides available.
REQ-00200 implemented Trailing slash enforcement behavior shall be controllable from a single configuration value such that enabling or disabling it does not require manual edits in multiple files.

Search

Search across pages and articles. Use arrow keys to navigate results.

Search across pages and articles.

Loading search...

Search is unavailable. Please try again later.

    No results for ""

    Try different keywords or fewer words.