SEO.md — SEO & Discoverability Audit

A real report, written by 70 · SEO & Discoverability, run against this repo — dogfood output, committed unedited. This is the artifact every brief ends in: findings that cite their evidence, ranked by severity, with a fix sketch each.

Date: 2026-07-09 Auditor: SEO & discoverability brief (70), read-only pass Prior run: none — no previous SEO.md existed; this is the first SEO audit. Method note: live fetches of goal-prompts.vercel.app were not possible from this environment (outbound blocked), so this audit was run against the built artifacts in the repo — index.html, b/, p/, examples/, sitemap.xml, robots.txt, raw/, sw.js — which are regenerated by build.py and match HEAD (clean tree), i.e. they are byte-for-byte what Vercel deploys. Routing behavior (cleanUrls, redirects, headers) was read from vercel.json.


1 · Verdict

Indexable and shareable today: yes. This is a fully static site with real SEO machinery, actually used: 180 URLs in sitemap.xml, a correct robots.txt, unique per-page <title> + meta description + canonical on every brief and playbook page (emitted by page() in build.py:758–787), per-brief 1200×630 OG images (og/*.png, 141 files, verified dimensions), no noindex anywhere, one h1 per page, no query-param duplicates (all catalog state lives in the URL hash), and cleanUrls: true in vercel.json collapsing .html variants. The 141 b/ pages are exemplary: full content in static HTML including the verbatim brief text, dense static cross-links ("pairs well with", family siblings, playbook links).

The single biggest leak: the homepage's catalog — the site's whole reason to rank — does not exist in its static HTML. The 141 brief cards and 35 playbook cards are rendered client-side from a ~507 KB inline const DATA blob (index.html:765, render code around index.html:966–1052). Outside <script> tags, index.html contains exactly one link to a brief page (/b/46) and one to a playbook (/p/codereview-collab). Non-rendering crawlers (most non-Google bots, LLM/AI-answer crawlers, social scrapers) see a homepage with no path to 176 detail pages, and even for Google the homepage passes almost no static link equity to them — discovery routes entirely through the sitemap. The same blob makes the homepage a 572 KB HTML document (~165 KB gzipped), the site's only real Core Web Vitals liability.


2 · Three page checks

PageWhat the crawler seesWhat the unfurl rendersGap
/ (index.html)Full head (unique title, description, canonical, SoftwareApplication JSON-LD at line 20), hero h1, section headings and prose — but the catalog grid at #catalog (line 577) is empty markup filled by JS; 1 static brief link, 1 static playbook link; 572 KB documentRich card: og.png 1200×630, og:title/description, summary_large_image — correctThe product (141 briefs) is invisible without JS; near-zero static internal linking; heaviest page on the site
/b/47 (b/47.html)Complete static article: breadcrumb, h1 "The Fixer", lede, phases, the full verbatim brief in a <pre>, related-brief and playbook links — all crawlable without JS; ~10 KB gzippedPer-brief card: og/47.png 1200×630, unique og:title/description, canonical https://goal-prompts.vercel.app/b/47Only polish: no JSON-LD (the visible breadcrumb has no BreadcrumbList), heading skip h2→h4
/examples/ (examples/index.html)Static list of real sample reports with title + meta description — fine for indexing; it is in the sitemapNothing — the page has no og:* tags, no twitter:card, and no <link rel="canonical"> (grep confirms zero matches)A shared link to the site's best proof asset unfurls as a bare URL

3 · Findings

SEO-1 · Homepage catalog invisible to non-rendering crawlers

  • Lens: 3 Indexable content / 2 Crawlability
  • Where: index.html (grid built by JS from const DATA at line 765; static HTML outside scripts links to only /b/46 and /p/codereview-collab); source: template.html
  • Impact: High — homepage anchor text and link equity to 176 detail pages is zero in static HTML; LLM crawlers and non-Google bots can't discover or summarize the catalog from the landing page; Google defers it to the render queue
  • Fix sketch: emit a static catalog into template.html at build time — a plain <ul>/card list of all 141 brief links (id · name · one-liner) grouped by family, which the existing JS then enhances or replaces on load; or a static <noscript> index; or a build-generated HTML sitemap page linked from the footer
  • Effort: M

SEO-2 · 572 KB homepage HTML (507 KB inline script)

  • Lens: 8 Performance signals
  • Where: index.html (572,093 bytes total; 506,961 bytes inside <script>, dominated by all 141 full brief bodies in const DATA; ~165 KB gzipped vs ~10 KB for a b/ page)
  • Impact: Medium-high — download + parse cost on mobile hurts INP/TBT proxies; the bodies are only needed at copy time
  • Fix sketch: strip body from the inline DATA and fetch raw/<id>.md (already deployed, network-only in sw.js) or a slimmed catalog.json on demand at copy time; keep offline copies via the service worker cache. Pairs naturally with SEO-1: static cards replace inline JSON
  • Effort: M

SEO-3 · One generic OG image for all 35 playbooks, Studio, and Vitals

  • Lens: 6 Social unfurls
  • Where: build.py:1106 passes {BASE}/og.png for every playbook page; confirmed in p/day1.html, studio.html, vitals.html (all og:image = /og.png)
  • Impact: Medium — playbooks are the shareable merchandising unit (storefront, badges, sponsors) yet every share renders the identical site-wide card, while briefs already get per-page og/NN.png
  • Fix sketch: extend scripts/og.py to render per-playbook cards (name, brief count, family color bars from build.FAMILY_COLORS) into og/p-<key>.png and wire them through playbook_detail
  • Effort: M

SEO-4 · /examples/ has no Open Graph tags and no canonical

  • Lens: 6 Social unfurls / 4 Canonical
  • Where: examples/index.html head (has title + description only; zero og:, twitter:, or canonical matches) — it is a sitemap URL (sitemap.xml line 6)
  • Impact: Medium — the "see a real report" proof page, linked from every detail-page footer, unfurls as a bare link when shared
  • Fix sketch: add the same og/twitter/canonical block the generated pages get (hand-add, or generate the page from build.py's page() helper)
  • Effort: S

SEO-5 · studio.html and vitals.html lack rel="canonical"

  • Lens: 4 Canonical & duplicates
  • Where: studio.html, vitals.html heads (both have full og/meta but no canonical; the generated pages all have one via build.py:774)
  • Impact: Low — cleanUrls: true in vercel.json 308-redirects .html variants, so real duplication risk is small; consistency and self-canonicalization are still worth having
  • Fix sketch: add <link rel="canonical" href="https://goal-prompts.vercel.app/studio"> (and /vitals) to the two source pages
  • Effort: S

SEO-6 · No structured data on the 141 brief pages or 35 playbook pages

  • Lens: 5 Structured data
  • Where: JSON-LD exists only on the homepage (index.html:20, SoftwareApplication); b/.html and p/.html have none (grep: 0 matches) despite visible breadcrumbs on every b/ page
  • Impact: Medium — 176 pages forgo BreadcrumbList (breadcrumb display in results) and content-type schema (Article/HowTo for briefs, ItemList for playbooks) that this content maps to cleanly
  • Fix sketch: emit BreadcrumbList + a content schema per page from brief_detail/playbook_detail in build.py — all the data (title, description, dates, family) is already in hand at build time
  • Effort: M

SEO-7 · Sitemap has no <lastmod>

  • Lens: 2 Crawlability
  • Where: sitemap.xml (180 <url> entries, <loc> only), generated at build.py:1389–1397
  • Impact: Low-medium — no freshness signal; crawlers recrawl all 180 URLs blindly and may be slow to pick up changed briefs
  • Fix sketch: emit <lastmod> per URL from each source file's git commit date (or build date as a floor) in the sitemap writer
  • Effort: S

SEO-8 · The entire repo is deployed and indexable — duplicate text surfaces

  • Lens: 2 Crawlability / 4 Duplicates
  • Where: vercel.json (outputDirectory: ".") serves everything: raw/*.md (verbatim duplicates of each b/ page's brief text), prompts/**/*.md (a third copy), root reports (BUGS.md, FUNNEL.md, …, linked from b/ pages as "see a real report"), build.py, tests/, docs/; no X-Robots-Tag anywhere
  • Impact: Low-medium — text/plain duplicates dilute crawl budget and can outrank the styled page for exact-phrase queries; bot fetches of /raw/ also pollute the fetch-count usage metric (sw.js:26, docs/usage-metrics.md)
  • Fix sketch: add vercel.json header rules — X-Robots-Tag: noindex for /raw/(.) and /prompts/(.) (they stay fetchable for agents, which is their purpose), and consider the same for root *.md
  • Effort: S

SEO-9 · Unfurl metadata gaps in the shared head template

  • Lens: 6 Social unfurls
  • Where: build.py:758–787 (page()), mirrored in template.html, studio.html, vitals.html: no og:site_name, no og:image:width/height/alt, no twitter:title/twitter:description
  • Impact: Low — most scrapers fall back to og:title/description; explicit tags remove ambiguity and add image alt for accessibility-aware unfurlers
  • Fix sketch: add the four tags once in page() and once in each hand-authored head
  • Effort: S

SEO-10 · Heading-level skip on detail pages

  • Lens: 7 Semantics & headings
  • Where: b/47.html (and siblings): h2 sections contain h4 card/lens titles with no h3 (phases do use h3, so the outline is inconsistent rather than flat)
  • Impact: Low — outline still parses; one h1 per page and descriptive link text are already in place everywhere checked
  • Fix sketch: promote card/lens h4s to h3 in brief_detail/playbook_detail (pure CSS restyle, no visual change)
  • Effort: S

4 · Quick wins (shippable this week)

  • SEO-4 — add og/twitter/canonical to examples/index.html; the site's proof page becomes shareable. (S)
  • SEO-5 — add canonicals to studio.html and vitals.html. (S)
  • SEO-7 — emit <lastmod> in the sitemap writer (build.py:1393). (S)
  • SEO-8X-Robots-Tag: noindex headers for /raw/ and /prompts/ in vercel.json; also cleans the usage metric. (S)
  • SEO-9og:site_name + image dimensions + twitter title/description in page(). (S)
  • Start SEO-1 — even a minimal static footer/index of all 141 /b/ links in template.html closes most of the discovery gap this week; the full static-card render can follow. (M, biggest reach)

Reach-blockers: none shipped today — nothing is unindexable and no noindex exists. SEO-1 is the closest thing: not a blocker for Google, but a real reach ceiling for every crawler that doesn't execute JS. Everything else is polish ranked by the traffic it touches.


Which of these fixes should I make? SEO-1 (static homepage catalog) is the highest-reach; the five quick wins (SEO-4, 5, 7, 8, 9) are a small, safe batch — or name any subset and I'll start there.