Back to blog
EngineeringJun 23, 2026· min read

Metadata Wiring Verification: When TDD Audits Catch the Silent Gaps

An automated TDD audit caught missing Open Graph metadata on two content-listing pages that would have launched with broken social sharing. Here's how verification tests turned 'probably fine' into provably complete.

We shipped OG images for all product pages two weeks ago. Manual testing showed cards rendering correctly on Twitter and LinkedIn. The task was marked done. But when sc-qa ran a comprehensive metadata audit as part of US-016-T04, it found something we'd missed: two pages that had titles but no OG blocks at all.

The /stream and /blog index pages were live with no openGraph or twitter metadata. Social scrapers would fall back to the root layout, but those generic cards weren't surfacing the page-specific titles or descriptions we'd written. The failure mode was silent: cards would render, just not with the right content.

What the Audit Found

sc-qa built a per-page metadata verification table covering all 11 routes. The table checked seven properties: title, description, og:image URL, og:type, twitter:card, twitter:images, and edge runtime declaration. Two pages failed completeness:

  • Finding F2: /stream had title + description but no openGraph or twitter blocks. Social cards would use root metadata instead of the stream-specific copy.
  • Finding F3: /blog had the same gap. The blog index would share with generic site metadata instead of blog-specific context.

Both were wired to use the root /opengraph-image route as a fallback — intentional for content-listing pages that don't need custom hero graphics. But the metadata blocks themselves were missing, which meant the page-specific title and description weren't being passed to the OG tags.

The Edge Runtime Drift

The audit also caught an architectural inconsistency. Five product OG image routes were missing the export const runtime = "edge" declaration:

  • /products/sabine/opengraph-image.tsx
  • /products/anti-strug/opengraph-image.tsx
  • /products/strugcity-sports/opengraph-image.tsx
  • /products/feedtumi/opengraph-image.tsx
  • /products/poppin/opengraph-image.tsx

Without the edge runtime export, these routes would serve as Node.js serverless functions instead of lightweight edge functions. That's not a correctness issue — the images still render — but it's wasteful. OG image generation is stateless and benefits from edge colocation near the requester (Twitter's scraper, LinkedIn's bot). The root route and /products/strug-works already had the export. The five newer pages didn't.

We added the declaration to all five. Two routes — /about and /team — intentionally use Fluid Compute (Node.js runtime) per CLAUDE.md policy and locked audit invariants from earlier PRs, so they were excluded from this change.

TDD Guards Lock It In

Fixing the gaps was straightforward. The harder problem: preventing regression. A future edit to /stream or /blog could silently remove the OG blocks, and we wouldn't notice until someone shared a broken link on social media.

So we added guard tests for both pages. The tests are simple string-match assertions against the source files:

  • expect(content).toContain('openGraph:')
  • expect(content).toContain('type: "website"')
  • expect(content).toContain('"/opengraph-image"')
  • expect(content).toContain('card: "summary_large_image"')

If someone refactors the metadata export and accidentally removes the OG block, the build fails. The test failures surface the exact missing properties. It's not sophisticated — just a basic existence check — but it catches the most common failure mode: inadvertent deletion during refactoring.

What's Next

All 11 pages in the audit table now have correct metadata wiring. All 9 OG image routes return 200 + image/png. The build emits no warnings. But the audit also surfaced two low-priority enhancements worth considering:

  • Dedicated OG images for /stream and /blog. Right now they fall back to the root card (Aurora gradient + Strug City wordmark). That's fine, but dedicated cards with stream or blog branding would make shares more contextual. Low priority — the current fallback is correct, just generic.
  • Fix missing publishedAt fields in Sanity blog posts. The build logs show warnings about blog posts with null publishedAt values. That's a pre-existing data quality issue in Sanity, not introduced by this PR. But it degrades the blog index sort order and could cause issues with RSS feeds. Worth backfilling.

For now, the OG metadata layer is complete and locked with tests. Every page has the metadata it needs for correct social sharing. The gaps are closed.