bcs-website/docs/adr/0003-seo-enforcement-with-zod.md
Brad Rodgers cd91c4b97c Initial commit: BlueCap Strategies Astro site
Static Astro 5 site for BlueCap Strategies with exact live-site content
parity, deployed to k3s via GitOps (ArgoCD).

- Page copy in Markdown content collections (services, focus-areas, pages,
  insights) with a Zod SEO schema enforced at build time
- Homepage + About restored to exact live copy; real live-site imagery
- Build-time SEO linter (scripts/seo-lint.mjs) and Playwright e2e suite
- Multi-stage Dockerfile (nginx serves dist/) and Kustomize manifests (k8s/)
- Per-agent robots.txt; config-driven PostHog + Umami analytics scaffold
- ADRs and engineering docs under docs/

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 17:40:46 -04:00

1.4 KiB
Raw Blame History

ADR-0003: Enforce SEO rules with Zod + build linter (not Pydantic)

Status: Accepted — 2026-07-15

Context

We want SEO/AEO rules (title/description length, canonical, OG image, structured data, single H1, alt text) to be enforced, not aspirational. The question of a Pydantic-style validator came up, but this is a TypeScript/Astro build — Pydantic is Python and would mean bolting a foreign runtime onto a JS pipeline.

Decision

Enforce in two layers, both native to the stack:

  1. Zod schema on content-collection frontmatter (Astro Content Collections already run on Zod). Encodes: title ≤ 55 chars, description 120158, valid canonical, required OG image, noindex flag, primaryTopic. Violations fail npm run build.
  2. Build-time SEO linter (scripts/seo-lint.mjs) over dist/**/*.html for rules Zod can't see in frontmatter: exactly one <h1>, heading order, canonical/title/description present, non-empty alt on every <img>, every JSON-LD block parses. Runs in CI (and locally via npm run seo:lint).

Do not introduce Python/Pydantic.

Consequences

  • Every commit is validated before ArgoCD deploys; "the rules are followed" is guaranteed by CI.
  • Rules live with the content schema and the build, in one language.
  • Requires the content collections from ADR-0002 to carry the shared seo schema.
  • Detailed rules in docs/seo-aeo-optimization.md §1.