The hard seams: internal linking with no orphans, a Hindi mirror without duplication, and per-page images at scale.
What breaks at scale?
Three words first. An orphan is a page nothing links to — readers can’t walk to it, crawlers barely find it. hreflang is a tag telling search engines “this Hindi page and that English page are the same content in two languages” so neither is punished as a copy. A sitemap is the machine-readable list of every page you want indexed. Small sites survive without any of this. At 200 pages and two languages, each one is load-bearing.
The internal chain: no orphans
Every page links onward in a chain — hub → cluster → related → unit → app — built by relatedFor in src/lib/seo.ts. It assembles up to 7 links per page in a fixed priority: the author’s declared links first, then cross-family pages sharing a topic, then the curriculum units the page draws from, then one same-family sibling, then the cluster quiz where one exists. Deterministic, capped, deduped — the same page always renders the same list, and no list is ever “10 random articles.”
Two guards keep the chain honest as it grows. A slug-collision check throws at build time if a dataset page claims a URL the curated taxonomy already owns. And tools/content-report.mjs writes docs/CONTENT_QA.md — links, orphans, and stats — so a page with nothing pointing at it shows up in a report instead of rotting quietly.
The sitemap and the project-site catch
@astrojs/sitemap generates the sitemap automatically, with one honest adaptation: the integration emits no freshness dates by default, so the config stamps every URL with the build date — documented as a deploy-level signal, not faked per-file precision. And GitHub Pages project sites carry a real catch worth knowing: only the user-site root robots.txt is honored by crawlers, so the sitemap must be submitted by hand in Search Console (and Bing Webmaster Tools). The README says exactly that, because the kind of fact you discover at 2am deserves to be written down at 2pm.
The Hindi mirror
The /hi/ pages reuse the same snapshots with a Hindi UI dictionary (src/i18n/hi.ts) — navigation, footer, CTA, and form chrome in Hindi, curriculum content untouched. Reciprocal hreflang runs both ways plus x-default, every mirror carries the Hindi language tag, and the flat Hindi nav strip keeps the section structure identical. Pages without a mirror fall back to an explicit translation widget with fallback text, so the line never renders empty. One sync updates both languages because there was never a second copy to update.
Per-page images at scale
83 pages carry their own Open Graph card, generated by tools/make-og-pages.mjs with manifest-driven lookup in the head component: Latin-only text (Devanagari rendering in generated images is unreliable), a default fallback for pages without one, and a checker script verifying every page resolves its card. Social previews and link unfurls read these — 83 consistent cards from one tool instead of 83 design files.
Deferred with reasons, not by accident
Three things the site deliberately does not do yet — each with written entry conditions, which is what separates “later” from “never”:
- Web audio. A generator script exists (batch text-to-speech with hash caching), but clips ship only after native-speaker review of every clip. Unreviewed pronunciation teaches mistakes at scale.
- Top word-pages. Skipped until pages have audio plus multiple examples each — the exact conditions that would keep them above the thin-content bar from Part 2.
- Shared content package. The future V2 has both the app and the site consuming one content location; V1’s committed snapshots are the honest stepping stone, with drift guarded by the counts in
meta.json.