From 144a76e26599208619512585fde6db0b0038151e Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 8 Aug 2026 19:22:02 +0200 Subject: [PATCH] fix(core): let sites override the collection sitemap route MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 37e848bf 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 #1089 (use case 3), which 37e848bf covered for the other two routes only. Co-Authored-By: Claude Fable 5 --- .changeset/sitemap-collection-route-override.md | 5 +++++ packages/core/src/astro/integration/routes.ts | 10 ++++++---- packages/core/tests/unit/astro/routes.test.ts | 15 +++++++++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 .changeset/sitemap-collection-route-override.md diff --git a/.changeset/sitemap-collection-route-override.md b/.changeset/sitemap-collection-route-override.md new file mode 100644 index 0000000000..2908b4b0d2 --- /dev/null +++ b/.changeset/sitemap-collection-route-override.md @@ -0,0 +1,5 @@ +--- +"emdash": patch +--- + +Skips the default `/sitemap-[collection].xml` route injection when the host site defines its own, matching the existing behaviour for `robots.txt` and `sitemap.xml`. diff --git a/packages/core/src/astro/integration/routes.ts b/packages/core/src/astro/integration/routes.ts index 93f0efdfa3..f859258f57 100644 --- a/packages/core/src/astro/integration/routes.ts +++ b/packages/core/src/astro/integration/routes.ts @@ -842,10 +842,12 @@ export function injectCoreRoutes( }); } - injectRoute({ - pattern: "/sitemap-[collection].xml", - entrypoint: resolveRoute("sitemap-[collection].xml.ts"), - }); + if (!options.srcDir || !hasUserDefinedPublicRoute(options.srcDir, "sitemap-[collection].xml")) { + injectRoute({ + pattern: "/sitemap-[collection].xml", + entrypoint: resolveRoute("sitemap-[collection].xml.ts"), + }); + } if (!options.srcDir || !hasUserDefinedPublicRoute(options.srcDir, "robots.txt")) { injectRoute({ diff --git a/packages/core/tests/unit/astro/routes.test.ts b/packages/core/tests/unit/astro/routes.test.ts index cb1bcc31c3..52fbef6c8b 100644 --- a/packages/core/tests/unit/astro/routes.test.ts +++ b/packages/core/tests/unit/astro/routes.test.ts @@ -111,6 +111,21 @@ describe("core media route injection", () => { ); }); + it("skips the collection sitemap route when the site defines its own", async () => { + await withTempSrcDir( + { + "pages/sitemap-[collection].xml.ts": "export const GET = () => new Response('');", + }, + (srcDir) => { + const routes = collectRoutePatterns(srcDir); + + expect(routes).not.toContain("/sitemap-[collection].xml"); + expect(routes).toContain("/sitemap.xml"); + expect(routes).toContain("/robots.txt"); + }, + ); + }); + it("detects index route files for root public route overrides", async () => { await withTempSrcDir( {