Skip to content

fix(core): let sites override the collection sitemap route - #2385

Open
danielmlr wants to merge 1 commit into
emdash-cms:mainfrom
danielmlr:fix/sitemap-collection-route-override
Open

fix(core): let sites override the collection sitemap route#2385
danielmlr wants to merge 1 commit into
emdash-cms:mainfrom
danielmlr:fix/sitemap-collection-route-override

Conversation

@danielmlr

Copy link
Copy Markdown
Contributor

What does this PR do?

#1386 added a hasUserDefinedPublicRoute() guard so a site's own robots.txt and sitemap.xml route files suppress the injected defaults — but left /sitemap-[collection].xml unguarded. It is injected unconditionally, so a site that ships its own sitemap-[collection].xml.ts gets Astro's duplicate-route warning ("A dynamic SSR route cannot be defined more than once"), and which handler answers rests on route-precedence luck. Astro has announced the collision will become a hard error in a future version.

Of the three SEO routes this is also the only one that serves content data (every published entry of every SEO-enabled collection, with a public cache header) — so it is the one a site is most likely to need to replace or close, e.g. a non-public deployment behind auth, or the per-collection customisations described in #1089.

This PR applies the same guard to the third route. existsSync treats the square brackets as literal path characters, so hasUserDefinedPublicRoute() needs no change. Sites without their own file see no difference; sites with one now get a clean override instead of a warning and undefined precedence.

The existing "skips root SEO routes that are defined by the site" test keeps asserting that a site-defined sitemap.xml.ts alone does not suppress the collection route; the new test covers the collection-specific override. The new test fails on the unpatched source.

Closes #1089#1386 implemented its Option C for two of the three routes; this covers the remaining one, which the issue lists as use case 3. With it, all three injected SEO routes can be replaced by shipping a route file, with no node_modules patching. (An explicit config option, the issue's Option A, would still be a reasonable follow-up, but is a design decision beyond this fix.)

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change) — tests/unit/astro/routes.test.ts, 9/9
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation (n/a — no admin UI change)
  • I have added a changeset
  • New features link to an approved Discussion (n/a — bug fix)

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: Claude Fable 5 (drafted and reviewed in separate sessions)

Screenshots / test output

New test against the unpatched source (proves it can fail):

FAIL tests/unit/astro/routes.test.ts > core media route injection > skips the collection sitemap route when the site defines its own
Tests  1 failed | 8 passed (9)

With the fix:

Tests  9 passed (9)

37e848b guarded the injection of robots.txt and sitemap.xml with
hasUserDefinedPublicRoute() so a site's own route file wins, but left
/sitemap-[collection].xml unguarded — the one of the three SEO routes
that serves content data (every published entry of every SEO-enabled
collection, with a public cache header). A site that defines its own
sitemap-[collection].xml.ts gets Astro's duplicate-route warning, and
which handler answers depends on route-precedence luck; Astro has
announced the collision will become a hard error.

Apply the same guard to the third route. existsSync treats the square
brackets as literal path characters, so the helper needs no change.

The existing "skips root SEO routes" test keeps asserting that defining
sitemap.xml.ts alone does NOT suppress the collection route; the new
test covers the collection-specific override. Verified the new test
fails on the unpatched source.

Requested in emdash-cms#1089 (use case 3), which 37e848b covered for the other
two routes only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Aug 8, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 144a76e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 17 packages
Name Type
emdash Patch
@emdash-cms/cloudflare Patch
@emdash-cms/sandbox-workerd Patch
@emdash-cms/plugin-mcp-smoke Patch
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/do-demo-site Patch
@emdash-cms/do-solo-demo-site Patch
@emdash-cms/admin Patch
@emdash-cms/auth Patch
@emdash-cms/blocks Patch
@emdash-cms/gutenberg-to-portable-text Patch
@emdash-cms/x402 Patch
create-emdash Patch
@emdash-cms/auth-atproto Patch
@emdash-cms/plugin-embeds Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added review/needs-review No maintainer or bot review yet area/core size/S labels Aug 8, 2026
@pkg-pr-new

pkg-pr-new Bot commented Aug 8, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@2385

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@2385

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/@emdash-cms/auth-atproto@2385

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@2385

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@2385

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/contentful-to-portable-text@2385

emdash

npm i https://pkg.pr.new/emdash@2385

create-emdash

npm i https://pkg.pr.new/create-emdash@2385

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@2385

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/@emdash-cms/plugin-cli@2385

@emdash-cms/plugin-types

npm i https://pkg.pr.new/@emdash-cms/plugin-types@2385

@emdash-cms/registry-client

npm i https://pkg.pr.new/@emdash-cms/registry-client@2385

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/@emdash-cms/registry-lexicons@2385

@emdash-cms/registry-verification

npm i https://pkg.pr.new/@emdash-cms/registry-verification@2385

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/@emdash-cms/sandbox-workerd@2385

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@2385

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@2385

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@2385

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@2385

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@2385

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@2385

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/@emdash-cms/plugin-field-kit@2385

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@2385

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@2385

commit: 144a76e

@ascorbic ascorbic added the bot:review Trigger an emdashbot code review on this PR label Aug 8, 2026

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the right fix for the right problem, and it follows the established pattern exactly.

#1089 asked for a way to override or disable the injected per-collection sitemap route; #1386 implemented the override guard for robots.txt and sitemap.xml but missed /sitemap-[collection].xml. This PR simply applies the same guard to the remaining route so sites can ship their own sitemap-[collection].xml.ts without Astro's duplicate-route warning. That is consistent with EmDash's convention of backing off when the host site defines a public route file, and the change stays additive (sites without an override see no difference).

What I checked:

  • The new condition in injectCoreRoutes mirrors the two existing guards exactly: !options.srcDir || !hasUserDefinedPublicRoute(options.srcDir, "sitemap-[collection].xml").
  • hasUserDefinedPublicRoute checks both pages/<basename>.<ext> and pages/<basename>/index.<ext> against the literal file path, so square brackets are treated as literal filename characters, which is correct here.
  • The existing test "skips root SEO routes that are defined by the site" still asserts that overriding sitemap.xml alone does not suppress the collection sitemap; the new test covers the collection-specific override and confirms the unaffected routes remain.
  • Changeset is a proper user-facing patch note for emdash.
  • The change touches only build-time route injection; no admin UI strings, no database queries, no localization, no authorization concerns, and no logged-out query-count impact.

I found no issues. The PR is clean and can be approved as-is.

@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Aug 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/core review/needs-review No maintainer or bot review yet size/S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Integration option to disable / replace auto-injected SEO routes (robots.txt, sitemap.xml)

2 participants