Skip to content

refactor(seo): declare sitemap hreflang alternates in each source - #540

Open
TheMeinerLP wants to merge 1 commit into
mainfrom
fix/sitemap-alternates
Open

TheMeinerLP wants to merge 1 commit into
mainfrom
fix/sitemap-alternates

Conversation

@TheMeinerLP

Copy link
Copy Markdown
Collaborator

Summary

The sitemap sources for index pages, team profiles and blog-author pages now declare their hreflang alternates explicitly, the same way the blog, project, POI and event sources already do. Before, @nuxtjs/sitemap inferred those alternates from sibling entries in one flat list, so the result was right only while both locales stayed in that list.

The reported symptom did not reproduce. I checked the live /__sitemap__/de-DE.xml and en-US.xml, and a local pnpm build + pnpm preview of main at 79f10b7: index pages, team profiles and author pages already carried de-DE, en-US and x-default. The only entry without all three is /de/blog/wer-moechte-plugins-adoptieren, which exists only in German and correctly lists only de-DE. So this PR does not repair an observed output bug; it makes the guarantee explicit and tested. That is why the type is refactor, not fix.

Root cause per source

  • index-pages.ts / indexPageSitemapEntries: the entry carried only loc and lastmod. The merge with the app's entry kept the app's alternates, so the output was correct, but the source did not state them.
  • team.ts: emitted { loc } only. Alternates came from the module's sibling inference. No lastmod, because the team data has no modification date. since is a membership year, not a change date, and is absent from the data, so it was not used.
  • blog-authors.ts / blogAuthorSitemapEntries: emitted { loc, lastmod? } only. Alternates came from the module's inference. lastmod was already derived from released articles and is kept.
  • events.ts: already explicit (translationKey pairing). Unchanged.

Changes

  • shared/utils/sitemapAlternates.ts (new): localizedAlternates(locales, hrefOf). One alternate per locale where the page exists, plus x-default = en when en is among them.
  • indexPageSitemap.ts: every dated index entry lists all locales, whatever their dates.
  • blogAuthors.ts: alternates only for the languages where the author has a released article.
  • teamProfile.ts: teamSitemapEntries(slugsByLocale); alternates only for the languages where the profile is listed (thin profiles and open positions are excluded, as before).
  • server/api/__sitemap__/team.ts: now a thin wrapper over teamSitemapEntries.

No lastmod was added to team profiles or static pages (/de, /about, /bluemap, /community, /events, /join, /team). No truthful date exists for them in the content, and build time was not used.

Before / after XML (/__sitemap__/de-DE.xml)

Index page /de/blog:

<!-- before -->
<url>
  <loc>https://onelitefeather.net/de/blog</loc>
  <lastmod>2026-10-14</lastmod>
  <xhtml:link rel="alternate" href="https://onelitefeather.net/de/blog" hreflang="de-DE" />
  <xhtml:link rel="alternate" href="https://onelitefeather.net/en/blog" hreflang="x-default" />
  <xhtml:link rel="alternate" href="https://onelitefeather.net/en/blog" hreflang="en-US" />
</url>
<!-- after -->
<url>
  <loc>https://onelitefeather.net/de/blog</loc>
  <lastmod>2026-10-14</lastmod>
  <xhtml:link rel="alternate" hreflang="de-DE" href="https://onelitefeather.net/de/blog" />
  <xhtml:link rel="alternate" hreflang="en-US" href="https://onelitefeather.net/en/blog" />
  <xhtml:link rel="alternate" hreflang="x-default" href="https://onelitefeather.net/en/blog" />
</url>

Team profile /de/team/saynax:

<!-- before -->
<url>
  <loc>https://onelitefeather.net/de/team/saynax</loc>
  <xhtml:link rel="alternate" href="https://onelitefeather.net/de/team/saynax" hreflang="de-DE" />
  <xhtml:link rel="alternate" href="https://onelitefeather.net/en/team/saynax" hreflang="x-default" />
  <xhtml:link rel="alternate" href="https://onelitefeather.net/en/team/saynax" hreflang="en-US" />
</url>
<!-- after: same alternate set, en-US before x-default, no lastmod -->
<url>
  <loc>https://onelitefeather.net/de/team/saynax</loc>
  <xhtml:link rel="alternate" hreflang="de-DE" href="https://onelitefeather.net/de/team/saynax" />
  <xhtml:link rel="alternate" hreflang="en-US" href="https://onelitefeather.net/en/team/saynax" />
  <xhtml:link rel="alternate" hreflang="x-default" href="https://onelitefeather.net/en/team/saynax" />
</url>

Author page /de/blog/author/themeinerlp:

<!-- before -->
<url>
  <loc>https://onelitefeather.net/de/blog/author/themeinerlp</loc>
  <lastmod>2026-10-14</lastmod>
  <xhtml:link rel="alternate" href="https://onelitefeather.net/de/blog/author/themeinerlp" hreflang="de-DE" />
  <xhtml:link rel="alternate" href="https://onelitefeather.net/en/blog/author/themeinerlp" hreflang="x-default" />
  <xhtml:link rel="alternate" href="https://onelitefeather.net/en/blog/author/themeinerlp" hreflang="en-US" />
</url>
<!-- after: same lastmod (newest released article of the author), same alternate set -->
<url>
  <loc>https://onelitefeather.net/de/blog/author/themeinerlp</loc>
  <lastmod>2026-10-14</lastmod>
  <xhtml:link rel="alternate" hreflang="de-DE" href="https://onelitefeather.net/de/blog/author/themeinerlp" />
  <xhtml:link rel="alternate" hreflang="en-US" href="https://onelitefeather.net/en/blog/author/themeinerlp" />
  <xhtml:link rel="alternate" hreflang="x-default" href="https://onelitefeather.net/en/blog/author/themeinerlp" />
</url>

Verified on the local preview: all <url> entries in both files have the expected alternates, no duplicate <loc>, and the German-only article keeps only de-DE.

Test results

  • New and updated unit tests (tests/shared/sitemapAlternates.spec.ts, teamProfile.spec.ts, blogAuthors.spec.ts, indexPageSitemap.spec.ts): written red first, then green. pnpm exec vitest run tests/shared tests/seo/team-thin-profiles.spec.ts: 92 passed.
  • pnpm test: 1412 tests pass. 46 test files fail to boot with the same errors as before the change. They are Nuxt-environment suites (// @vitest-environment nuxt): nuxt/dist/diagnostics/core.js is missing from the installed nuxt package, and virtual:intlify-i18n-0 cannot be resolved. None of them touches the sitemap code, and the lockfile is unchanged. I could not get them running in this environment.
  • pnpm quality: ESLint errors 186 (baseline 186), warnings 17, TS errors 3. No regression, baseline unchanged.
  • pnpm build: passes.

https://claude.ai/code/session_01A9Zgvt4WR2zDBkAHaK1FjL

The index-page, team and blog-author sitemap sources emitted no
alternates of their own. @nuxtjs/sitemap inferred them from sibling
entries in the same flat list, so the output was right only as long
as both locales stayed in that list. Each source now lists its
alternates explicitly, as the blog, project, POI and event sources
already do:

- index pages list both locales, since every index page exists in each;
- team profiles and blog-author pages list only the languages in which
  the page is listed (a thin profile or an author without a released
  article in a language is not offered there);
- x-default is the en URL, as in the pages' own <head>.

The team data has no modification date, so team profiles get no lastmod.
Static pages keep none either: no truthful date exists for them.

Generated sitemap output keeps the same alternate sets; only the order of
the attributes and of the alternates within an entry changed.

Claude-Session: https://claude.ai/code/session_01A9Zgvt4WR2zDBkAHaK1FjL
@TheMeinerLP
TheMeinerLP force-pushed the fix/sitemap-alternates branch from dfa23d4 to fa301fa Compare October 14, 2026 21:05
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
launchpad fa301fa Commit Preview URL

Branch Preview URL
Oct 14 2026, 09:08 PM

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