Skip to content

feat(website): /features index + multilingual sitemap (hreflang + lastmod + sitemap-index + robots.txt)#202

Merged
montfort merged 1 commit into
mainfrom
feat/sitemap-features-index-hreflang
May 23, 2026
Merged

feat(website): /features index + multilingual sitemap (hreflang + lastmod + sitemap-index + robots.txt)#202
montfort merged 1 commit into
mainfrom
feat/sitemap-features-index-hreflang

Conversation

@montfort
Copy link
Copy Markdown
Contributor

Summary

SEO improvement so crawlers (Google + friends) understand the site's information tree (features / docs / blog) and how each section's content branches into the three shipped locales (en / es / zh-CN).

The trigger: the sitemap analysis from the previous turn showed three gaps — no `/features` aggregator page (asymmetric with `/blog` and `/docs`), no hreflang annotations relating the per-locale URLs, and no `robots.txt` to make the three per-locale sitemaps discoverable from a single entry point.

What changed

1. `/features` index page (consistency with /blog and /docs)

  • `website/src/pages/features/{index.tsx, index.module.css}` — new aggregator page that renders `FeatureGrid` wrapped in a small header (eyebrow + H1 + intro). The nine `/features/` pages now have a parent index, matching the pattern `/blog` and `/docs` already had.
  • `website/src/components/FeaturesLayout/index.tsx` gains a sidebar "Overview" link back to the new `/features` index, with active-state detection that doesn't false-match on `/features/`.
  • i18n — six new keys per locale (`features.index.eyebrow`, `features.index.title`, `features.index.intro`, `features.sidebar.overview`, `meta.features.title`, `meta.features.description`) in `website/i18n/{es,zh-CN}/code.json`.

2. Sitemap plugin: hreflang + lastmod

  • `docusaurus.config.ts` preset.classic.sitemap — new `createSitemapItems` that wraps the default emitter and adds `xhtml:link rel="alternate" hreflang="..."` per URL for all three locales + `x-default`. Google uses these to cluster per-locale URLs as translations (not duplicates).
  • `lastmod: 'date'` + `showLastUpdateTime: true` on the docs and blog plugins + `future.experimental_vcs: 'git-eager'` so the sitemap plugin can read git history per file efficiently.

3. Static SEO files

  • `website/static/sitemap-index.xml` — aggregates the three per-locale sitemaps (`/sitemap.xml`, `/es/sitemap.xml`, `/zh-CN/sitemap.xml`) so crawlers discover all locales from one entry point.
  • `website/static/robots.txt` — declares `Sitemap: https://straymark.dev/sitemap-index.xml\`. Without this, the multi-sitemap setup is not auto-discoverable (Google would only crawl the canonical `/sitemap.xml` and miss `/es` + `/zh-CN` unless submitted manually to Search Console).

Local build verification

After `npm run build`:

Sitemap URLs hreflang links lastmod Notes
`build/sitemap.xml` 80 320 23 EN canonical
`build/es/sitemap.xml` 80 320 16 ES
`build/zh-CN/sitemap.xml` 80 320 16 zh-CN
  • 80 URLs per locale (was 79; +1 for the new `/features` index).
  • 320 hreflang annotations per sitemap = 80 URLs × 4 alternates (en + es + zh-CN + x-default). 960 total across the three sitemaps.
  • lastmod populated on all content pages (docs + blog). Aggregator pages (`/blog`, `/blog/tags/*`, `/blog/archive`, etc.) correctly have no lastmod — they don't map to a single source file.
  • `build/{sitemap-index.xml, robots.txt}` present at root.
  • `/features` renders correctly in all three locales: title, H1, and eyebrow translated.

Sample `` entry (the new `/features`):

```xml

https://straymark.dev/features
weekly
0.5
<xhtml:link rel="alternate" hreflang="en" href="https://straymark.dev/features"/>
<xhtml:link rel="alternate" hreflang="es" href="https://straymark.dev/es/features"/>
<xhtml:link rel="alternate" hreflang="zh-CN" href="https://straymark.dev/zh-CN/features"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://straymark.dev/features"/>

```

Test plan

  • `npm run typecheck` clean
  • `npm run build` clean for all three locales
  • Sitemap output verified locally (counts above)
  • `/features` index renders with translated metadata in en/es/zh-CN
  • After merge + deploy: verify `curl -sI https://straymark.dev/{robots.txt, sitemap-index.xml, features}` all return 200
  • After merge + deploy: submit `sitemap-index.xml` to Search Console (optional accelerator; robots.txt makes it crawl-discoverable automatically over time)

Known limitations (acceptable for v1)

  • lastmod on static pages (`src/pages/*`: `/features`, `/quickstart`, `/privacy`, `/`) is not populated. The pages plugin doesn't propagate `metadata.lastUpdatedAt`. Low SEO impact (Google ignores lastmod when not trusted; pages change rarely anyway). Can be added in a follow-up if needed.
  • lastmod on translated docs is partial — the docs plugin reads from the synced location (`website/i18n/{locale}/.../current/`) which is gitignored, so git lookup returns no timestamp. Fixing this would require either modifying `scripts/sync-docs-i18n.ts` to preserve the canonical source's git timestamp on the synced file, or pointing the sitemap to the canonical source. Out of scope for this PR.

🤖 Generated with Claude Code

…+ lastmod + sitemap-index + robots.txt)

SEO improvement so crawlers understand the site's information tree
(features / docs / blog) and how each section's content branches into
the three shipped locales (en / es / zh-CN).

Changes:

- **New /features index page** (website/src/pages/features/{index.tsx,
  index.module.css}). Renders FeatureGrid wrapped in a thin header
  (eyebrow + H1 + intro) so the nine /features/<slug> pages now have
  a parent aggregator, matching the pattern /blog and /docs already
  have. i18n-ready via translate() with localized OG metadata.
- **FeaturesLayout sidebar gains an "Overview" link** back to the new
  /features index, with active-state detection that doesn't false-match
  on /features/<slug> subpaths.
- **Sitemap plugin enriched** via createSitemapItems: every URL in each
  per-locale sitemap now carries `xhtml:link rel="alternate"
  hreflang="..."` entries for all three locales + an x-default — Google
  uses these to cluster the per-locale URLs as translations rather than
  duplicates. 80 URLs × 4 alternates × 3 sitemaps = 960 hreflang
  annotations emitted.
- **lastmod populated** for docs and blog posts via showLastUpdateTime
  on both content plugins + experimental_vcs: 'git-eager' on the
  future config so git history reads are eager-cached at build start.
  Aggregator pages (blog tag indexes, archive, author pages) correctly
  have no lastmod — they don't map to a single source file.
- **sitemap-index.xml** (static, website/static/) aggregates the three
  per-locale sitemaps so crawlers discover all locales from one entry
  point.
- **robots.txt** (static, website/static/) declares `Sitemap:` pointing
  at sitemap-index.xml. Without this, the multi-sitemap setup is not
  auto-discoverable.
- **i18n keys added** for the /features page (eyebrow, H1, intro, OG
  title, OG description, sidebar Overview label) in es + zh-CN code.json.

Build verified locally: 80 URLs × 3 locales, hreflang on every URL,
lastmod on 23 EN / 16 ES / 16 zh-CN URLs (all content; aggregators
correctly skipped).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@montfort montfort merged commit f1b5907 into main May 23, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant