Skip to content

seo: drop tag archives from sitemap, add hreflang and BlogPosting schema - #25

Merged
foxis merged 1 commit into
mainfrom
seo/technical-crawl-and-schema
Aug 16, 2026
Merged

seo: drop tag archives from sitemap, add hreflang and BlogPosting schema#25
foxis merged 1 commit into
mainfrom
seo/technical-crawl-and-schema

Conversation

@foxis

@foxis foxis commented Aug 16, 2026

Copy link
Copy Markdown

Three technical SEO fixes, each measured against a local build of Hugo 0.163.3 (the version pages.yml pins) before and after. No content changes — titles and copy are handled separately.

1. Tag archives were 66% of the sitemap

The EN sitemap carried 1392 URLs, of which 916 were tag term pages against 449 real content URLs. LT carried 346. On a site with ~93 referring domains, that spends most of Googlebot's crawl allowance re-reading near-empty archives instead of finding new posts.

blog/layouts/sitemap.xml now filters term and taxonomy pages belonging to the tags taxonomy. Categories (22) and series (5) are curated, so they stay listed.

Tag pages remain crawlable and indexable — they are only delisted from the sitemap, so any tag page that already earns traffic keeps it. (/tags/diffraction-grating/ currently ranks, so a blanket noindex was deliberately not used.)

Sitemap Before After
EN total 1392 476
— tag pages 916 0
— content 449 449
— categories 22 22
— series 5 5
LT total 346 99

Verified no /tags/ URL leaks through. The xhtml:link hreflang alternates that Hugo's embedded template emits are preserved deliberately — they were the site's only hreflang signal, and dropping them while rewriting the template would have been a silent regression.

2. No hreflang in the HTML head

Every post ships EN and LT, but alternates were declared only inside the sitemap. Head tags are what Google treats as authoritative for a page reached by internal link, backlink or share.

Added reciprocal alternates plus x-default, with the default language resolved from hugo.Sites.Default rather than hardcoded to en. Verified both language versions emit an identical, reciprocal set:

<link rel="alternate" hreflang="en" href="https://itohi.com/fpv/edgetx-cockpit-voice-rebuild/">
<link rel="alternate" hreflang="x-default" href="https://itohi.com/fpv/edgetx-cockpit-voice-rebuild/">
<link rel="alternate" hreflang="lt" href="https://itohi.com/lt/fpv/edgetx-cockpit-voice-rebuild/">

3. No Article schema anywhere

Every URL on the site emitted only Person + WebSite. No page ever declared a headline, publish date, modified date, author or section — Google had to infer all of it from rendered HTML. That also left the site ineligible for article rich results and gave no explicit freshness signal for a series that gets revised.

Added BlogPosting for article pages with datePublished, dateModified, wordCount, articleSection, keywords, and series as CreativeWorkSeries. Excluded from taxonomy pages (verified: tag pages emit no BlogPosting).

Built with dict + jsonify rather than hand-written JSON so quotes and non-ASCII escape correctly instead of producing invalid JSON-LD. Verified the LT output parses and Lithuanian diacritics land as valid \u escapes.

Notes

  • Used .Language.Locale instead of the .Language.LanguageCode the embedded template uses, since that was deprecated in Hugo 0.158. Build is warning-clean; before this change the build emitted three deprecation warnings.
  • Page counts unchanged (EN 2403 / LT 632), so nothing was dropped from the build itself.
  • Expect Search Console "Discovered — currently not indexed" to fall and crawl requests to shift toward posts over the following few weeks. Indexed tag pages stay indexed; this changes crawl priority, not the index.

Three measured problems, verified against a local build of Hugo 0.163.3
(the version pages.yml pins) before and after.

1. Tag archives dominated the sitemap.

   EN sitemap held 1392 URLs, of which 916 (66%) were tag term pages against
   449 real content URLs. LT held 346. On a site with ~93 referring domains
   that spends most of Googlebot's crawl allowance re-reading near-empty
   archives instead of discovering new posts.

   layouts/sitemap.xml now filters term and taxonomy pages for the tags
   taxonomy. Categories (22) and series (5) are curated and stay listed.
   Tag pages remain crawlable and indexable, they are only delisted, so any
   tag page already earning traffic keeps it.

   Measured: EN 1392 -> 476, LT 346 -> 99. No /tags/ URL leaks through.
   The xhtml:link hreflang alternates the embedded template emits are
   preserved deliberately, they were the site's only hreflang signal.

2. No hreflang in the HTML head.

   The site ships EN and LT for every post and declared alternates only
   inside the sitemap. Head tags are what Google treats as authoritative
   for a page reached by internal link, backlink or share. Added reciprocal
   alternates plus x-default resolved from hugo.Sites.Default rather than
   hardcoded.

3. No Article schema anywhere.

   Every URL emitted only Person + WebSite, so no page ever declared a
   headline, publish date, modified date, author or section. Added
   BlogPosting for article pages with datePublished, dateModified,
   wordCount, articleSection, series as CreativeWorkSeries, and keywords.

   Built with dict + jsonify rather than hand-written JSON so quotes and
   non-ASCII escape correctly. Verified the LT output parses and that
   Lithuanian diacritics land as valid \u escapes.

Used .Language.Locale over the .Language.LanguageCode the embedded template
uses, since that was deprecated in 0.158. Build is warning-clean.
@cursor

cursor Bot commented Aug 16, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

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