Skip to content

SEO: templates, config, and the generators that feed them - #852

Merged
richarddushime merged 1 commit into
mainfrom
seo-fixes-templates
Aug 5, 2026
Merged

SEO: templates, config, and the generators that feed them#852
richarddushime merged 1 commit into
mainfrom
seo-fixes-templates

Conversation

@LukasWallrich

@LukasWallrich LukasWallrich commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Splits #850 into a reviewable template change (this PR) and the generated content it implies (#853). Original work by @Keegan-Vaz; this PR carries the same templates plus the generator fixes that make them stick.

Why the split

#850 was 1,450 files. Twelve of them were the actual change; the other 1,438 were generated output — and neither set of edits survived. content/curated_resources/ is deleted and rewritten from a Google Sheet by the daily data-processing workflow, so the 136 tag cleanups would have reverted within 24 hours. content/glossary/ is deleted and rewritten by _create_glossaries.py, which was never taught to emit en_title, so the next regeneration would have silently dropped hreflang from all 1,300 glossary pages.

Both are now fixed at the source.

What's here

Templates and config — metadata fallbacks, short titles, Markdown-free descriptions, Organization JSON-LD, thin-tag noindex + sitemap exclusion behind one shared predicate, per-page language resolution, glossary hreflang, BreadcrumbList, CollectionPage/ItemList, preconnect hints, per-page Leaflet, banner alt. See the commit message for the detail.

content/resources/resource.py — drops placeholder tag entries ("", "-", ".") as the resources are generated. Verified against the live sheet: 0 placeholder entries remain, 135 rows get tags: [], 1,686 real terms and all other columns untouched.

content/glossary/_create_glossaries.py — emits en_title from the EN_title column it already reads.

Fixes found while reviewing #850

  • The sitemap override did not parse as XML. The <?xml …?> declaration was written as literal template text; Hugo escapes it to &lt;?xml, and xmllint rejects the whole document. That made the headline indexation fix strictly worse than the sitemap it replaced. Now emitted through safeHTML.
  • layouts/404.html was reverted to its pre-Fix 404 redirects appending 'undefined' (e.g. /replication_handbook) #851 state. Restored.

Testing

Full hugo build, no warnings. Sitemap validates (xmllint), 3,174 URLs, down from 4,631. 0 empty meta descriptions, down from 58. BreadcrumbList on 3,784 pages with no double-emission on /clusters/. Arabic glossary pages render <html lang="ar" dir="rtl">. /tag/-/ is gone.

Merge order

Either order works — hreflang no-ops until en_title exists in the content, so nothing breaks. #853 is stacked on this branch.

Not addressed

og:image is still a 4000x2250 WebP (needs a generated 1200x630 raster); og:locale emits bare language tags where Open Graph wants language_TERRITORY; highlight.js still loads on every page. The tag_index_min_pages = 5 floor de-indexes ~1,500 tag archives — that's a content-strategy call worth a maintainer's explicit sign-off rather than something to inherit from a bug-fix PR.

🤖 Generated with Claude Code

Template and configuration half of #850, split out so it can be reviewed
without the ~1,400 generated content files it implies.

Metadata:
- The description fallback in site_head.html tested branch order rather
  than the result, so widget-composed pages — whose `.Summary` is empty
  despite `.IsPage` being true — never reached the site-wide default.
  58 of 119 real pages shipped an empty meta description; now 0.
- Descriptions render Markdown before flattening, so `**bold**` and link
  syntax stop reaching search results.
- Pages use a short `short_title` suffix instead of spending 61 of the
  ~60 displayed characters on the full organisation name. The home page
  still spells it out.
- Organization JSON-LD pointed `image` at a 404 (`media_dir` prefixed
  onto a path already under `static/`) and omitted `sameAs`.

Indexation:
- Tags are free text on curated resources and ~74% are used exactly
  once, so ~1,500 near-empty archives dominated the sitemap. Terms below
  `tag_index_min_pages` are now `noindex, follow` and excluded from the
  sitemap, both reading one shared predicate so they cannot disagree.
  Sitemap: 4,631 -> 3,174 URLs.
- resource.py drops placeholder tag entries ("", "-", ".") at the
  source. These were aggregating into a meaningless /tag/-/ archive.
  Fixing the generator rather than its output matters because
  content/curated_resources is deleted and rewritten by the daily
  data-processing workflow.

Internationalisation:
- ~1,000 glossary terms in German, Arabic, Turkish and Chinese were
  served as `lang="en-US"`, with Arabic rendering left-to-right.
  Language is resolved per page, so Arabic gets `lang="ar" dir="rtl"`.
- The five glossaries are parallel directories rather than Hugo language
  sites, so `.Translations` is empty and no hreflang existed. Terms are
  grouped on `en_title` instead, which _create_glossaries.py now emits
  from the EN_title column it already reads. The content that carries
  the field follows in its own PR.

Structured data and page weight:
- BreadcrumbList extended from 11 cluster pages to 3,784.
- Section landings emit CollectionPage + ItemList; they had none.
- Added preconnect for cdnjs and Google Fonts; gated Leaflet to the
  pages that actually render a map.
- Post banners fell back to an empty `alt`; now use the post title.

The sitemap override writes its XML declaration through `safeHTML`;
emitted as literal template text, Hugo escapes it to `&lt;?xml` and the
whole document stops parsing as XML.

Not addressed: og:image remains a 4000x2250 WebP (needs a generated
1200x630 raster), og:locale still emits bare language tags where Open
Graph wants language_TERRITORY, and highlight.js loads on every page.

Co-Authored-By: Keegan Vaz <keegangeorgevaz@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

👍 All image files/references (if any) are in webp format, in line with our policy.

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

✅ Spell Check Passed

No spelling issues found when checking 13 changed file(s)! 🎉

@LukasWallrich

Copy link
Copy Markdown
Contributor Author

Staging Deployment Status

This PR has been successfully deployed to staging as part of an aggregated deployment.

Deployed at: 2026-08-05 14:25:18 UTC
Staging URL: https://staging.forrt.org

The staging site shows the combined state of all compatible open PRs.

@LukasWallrich

Copy link
Copy Markdown
Contributor Author

@richarddushime I believe this is ready to merge (and then #853 that is implied by it)

@richarddushime
richarddushime merged commit 8f10116 into main Aug 5, 2026
5 checks passed
@richarddushime
richarddushime deleted the seo-fixes-templates branch August 5, 2026 15:30
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.

2 participants