diff --git a/.changeset/strange-forks-exist.md b/.changeset/strange-forks-exist.md new file mode 100644 index 000000000000..4fa5b0277c20 --- /dev/null +++ b/.changeset/strange-forks-exist.md @@ -0,0 +1,5 @@ +--- +"astro": patch +--- + +Fix an incorrect conflict resolution between pages generated from static routes and rest parameters diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts index 6a9f0610c0fd..3a1b2939a81a 100644 --- a/packages/astro/src/core/build/generate.ts +++ b/packages/astro/src/core/build/generate.ts @@ -326,7 +326,7 @@ async function getPathsForRoute( let paths: Array = []; if (route.pathname) { paths.push(route.pathname); - builtPaths.add(route.pathname); + builtPaths.add(removeTrailingForwardSlash(route.pathname)); } else { const staticPaths = await callGetStaticPaths({ mod, diff --git a/packages/astro/src/core/routing/manifest/generator.ts b/packages/astro/src/core/routing/manifest/generator.ts index 60614f2e5271..2a3e18ade29b 100644 --- a/packages/astro/src/core/routing/manifest/generator.ts +++ b/packages/astro/src/core/routing/manifest/generator.ts @@ -37,5 +37,13 @@ export function getRouteGenerator( trailing = '/'; } const toPath = compile(template + trailing); - return toPath; + return (params: object): string => { + const path = toPath(params); + + // When generating an index from a rest parameter route, `path-to-regexp` will return an + // empty string instead "/". This causes an inconsistency with static indexes that may result + // in the incorrect routes being rendered. + // To fix this, we return "/" when the path is empty. + return path || '/'; + }; } diff --git a/packages/astro/test/dynamic-route-collision.test.js b/packages/astro/test/dynamic-route-collision.test.js new file mode 100644 index 000000000000..ca78af9b5b5c --- /dev/null +++ b/packages/astro/test/dynamic-route-collision.test.js @@ -0,0 +1,59 @@ +import assert from 'node:assert/strict'; +import { before, describe, it } from 'node:test'; +import * as cheerio from 'cheerio'; +import { loadFixture } from './test-utils.js'; + +describe('Dynamic route collision', () => { + /** @type {import('./test-utils').Fixture} */ + let fixture; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/dynamic-route-collision', + }); + + await fixture.build().catch(console.log); + }); + + it('Builds a static route when in conflict with a dynamic route', async () => { + const html = await fixture.readFile('/about/index.html'); + const $ = cheerio.load(html); + assert.equal($('h1').text(), 'Static About'); + }); + + it('Builds a static route when in conflict with a spread route', async () => { + const html = await fixture.readFile('/who/index.html'); + const $ = cheerio.load(html); + assert.equal($('h1').text(), 'Static Who We Are'); + }); + + it('Builds a static nested index when in conflict with a spread route', async () => { + const html = await fixture.readFile('/tags/index.html'); + const $ = cheerio.load(html); + assert.equal($('h1').text(), 'Static Tags Index'); + }); + + it('Builds a static root index when in conflict with a spread route', async () => { + const html = await fixture.readFile('/index.html'); + const $ = cheerio.load(html); + assert.equal($('h1').text(), 'Static Index'); + }); + + it('Builds a static index a nested when in conflict with a dynamic+spread route', async () => { + const html = await fixture.readFile('/en/index.html'); + const $ = cheerio.load(html); + assert.equal($('h1').text(), 'Dynamic-only Localized Index'); + }); + + it('Builds a dynamic route when in conflict with a spread route', async () => { + const html = await fixture.readFile('/blog/index.html'); + const $ = cheerio.load(html); + assert.equal($('h1').text(), 'Dynamic Blog'); + }); + + it('Builds the highest priority route out of two conflicting dynamic routes', async () => { + const html = await fixture.readFile('/order/index.html'); + const $ = cheerio.load(html); + assert.equal($('h1').text(), 'Order from A'); + }); +}); diff --git a/packages/astro/test/fixtures/dynamic-route-collision/package.json b/packages/astro/test/fixtures/dynamic-route-collision/package.json new file mode 100644 index 000000000000..35848efb79b6 --- /dev/null +++ b/packages/astro/test/fixtures/dynamic-route-collision/package.json @@ -0,0 +1,8 @@ +{ + "name": "@test/dynamic-route-collision", + "version": "0.0.0", + "private": true, + "dependencies": { + "astro": "workspace:*" + } +} diff --git a/packages/astro/test/fixtures/dynamic-route-collision/src/pages/[...slug].astro b/packages/astro/test/fixtures/dynamic-route-collision/src/pages/[...slug].astro new file mode 100644 index 000000000000..89498c2d9c3f --- /dev/null +++ b/packages/astro/test/fixtures/dynamic-route-collision/src/pages/[...slug].astro @@ -0,0 +1,35 @@ +--- +export async function getStaticPaths() { + const pages = [ + { + slug: undefined, + title: 'Rest Index', + }, + { + slug: 'blog', + title: 'Rest Blog', + }, + { + slug: 'who', + title: 'Rest Who We Are', + }, + ]; + return pages.map(({ slug, title }) => { + return { + params: { slug }, + props: { title }, + }; + }); +} + +const { title } = Astro.props; +--- + + + + {title} + + +

{title}

+ + diff --git a/packages/astro/test/fixtures/dynamic-route-collision/src/pages/[aOrder].astro b/packages/astro/test/fixtures/dynamic-route-collision/src/pages/[aOrder].astro new file mode 100644 index 000000000000..8957a3dc8b25 --- /dev/null +++ b/packages/astro/test/fixtures/dynamic-route-collision/src/pages/[aOrder].astro @@ -0,0 +1,27 @@ +--- +export async function getStaticPaths() { + const pages = [ + { + page: 'order', + title: 'Order from A', + }, + ]; + return pages.map(({ page, title }) => { + return { + params: { 'aOrder': page }, + props: { title }, + }; + }); +} + +const { title } = Astro.props; +--- + + + + {title} + + +

{title}

+ + diff --git a/packages/astro/test/fixtures/dynamic-route-collision/src/pages/[bOrder].astro b/packages/astro/test/fixtures/dynamic-route-collision/src/pages/[bOrder].astro new file mode 100644 index 000000000000..1b0eb30d8b9e --- /dev/null +++ b/packages/astro/test/fixtures/dynamic-route-collision/src/pages/[bOrder].astro @@ -0,0 +1,27 @@ +--- +export async function getStaticPaths() { + const pages = [ + { + page: 'order', + title: 'Order from B', + }, + ]; + return pages.map(({ page, title }) => { + return { + params: { 'bOrder': page }, + props: { title }, + }; + }); +} + +const { title } = Astro.props; +--- + + + + {title} + + +

{title}

+ + diff --git a/packages/astro/test/fixtures/dynamic-route-collision/src/pages/[locale]/[...page].astro b/packages/astro/test/fixtures/dynamic-route-collision/src/pages/[locale]/[...page].astro new file mode 100644 index 000000000000..1cdd7f766786 --- /dev/null +++ b/packages/astro/test/fixtures/dynamic-route-collision/src/pages/[locale]/[...page].astro @@ -0,0 +1,28 @@ +--- +export async function getStaticPaths() { + const pages = [ + { + locale: 'en', + page: undefined, + title: 'Dynamic+Rest Localized Index', + }, + ]; + return pages.map(({ page, locale, title }) => { + return { + params: { page, locale }, + props: { title }, + }; + }); +} + +const { title } = Astro.props; +--- + + + + {title} + + +

{title}

+ + diff --git a/packages/astro/test/fixtures/dynamic-route-collision/src/pages/[locale]/index.astro b/packages/astro/test/fixtures/dynamic-route-collision/src/pages/[locale]/index.astro new file mode 100644 index 000000000000..425c55f740cd --- /dev/null +++ b/packages/astro/test/fixtures/dynamic-route-collision/src/pages/[locale]/index.astro @@ -0,0 +1,27 @@ +--- +export async function getStaticPaths() { + const pages = [ + { + locale: 'en', + title: 'Dynamic-only Localized Index', + }, + ]; + return pages.map(({ page, locale, title }) => { + return { + params: { page, locale }, + props: { title }, + }; + }); +} + +const { title } = Astro.props; +--- + + + + {title} + + +

{title}

+ + diff --git a/packages/astro/test/fixtures/dynamic-route-collision/src/pages/[page].astro b/packages/astro/test/fixtures/dynamic-route-collision/src/pages/[page].astro new file mode 100644 index 000000000000..433198cd721f --- /dev/null +++ b/packages/astro/test/fixtures/dynamic-route-collision/src/pages/[page].astro @@ -0,0 +1,31 @@ +--- +export async function getStaticPaths() { + const pages = [ + { + page: 'about', + title: 'Dynamic About', + }, + { + page: 'blog', + title: 'Dynamic Blog', + }, + ]; + return pages.map(({ page, title }) => { + return { + params: { page }, + props: { title }, + }; + }); +} + +const { title } = Astro.props; +--- + + + + {title} + + +

{title}

+ + diff --git a/packages/astro/test/fixtures/dynamic-route-collision/src/pages/about.astro b/packages/astro/test/fixtures/dynamic-route-collision/src/pages/about.astro new file mode 100644 index 000000000000..16698abc16ae --- /dev/null +++ b/packages/astro/test/fixtures/dynamic-route-collision/src/pages/about.astro @@ -0,0 +1,8 @@ + + + Static About Page + + +

Static About

+ + diff --git a/packages/astro/test/fixtures/dynamic-route-collision/src/pages/index.astro b/packages/astro/test/fixtures/dynamic-route-collision/src/pages/index.astro new file mode 100644 index 000000000000..043c4a5c963f --- /dev/null +++ b/packages/astro/test/fixtures/dynamic-route-collision/src/pages/index.astro @@ -0,0 +1,8 @@ + + + Static Index Page + + +

Static Index

+ + diff --git a/packages/astro/test/fixtures/dynamic-route-collision/src/pages/tags/[...page].astro b/packages/astro/test/fixtures/dynamic-route-collision/src/pages/tags/[...page].astro new file mode 100644 index 000000000000..fa7f87d3a683 --- /dev/null +++ b/packages/astro/test/fixtures/dynamic-route-collision/src/pages/tags/[...page].astro @@ -0,0 +1,27 @@ +--- +export async function getStaticPaths() { + const pages = [ + { + page: undefined, + title: 'Rest Tag Index', + }, + ]; + return pages.map(({ page, title }) => { + return { + params: { tag: page }, + props: { title }, + }; + }); +} + +const { title } = Astro.props; +--- + + + + {title} + + +

{title}

+ + diff --git a/packages/astro/test/fixtures/dynamic-route-collision/src/pages/tags/index.astro b/packages/astro/test/fixtures/dynamic-route-collision/src/pages/tags/index.astro new file mode 100644 index 000000000000..0f86c53130b5 --- /dev/null +++ b/packages/astro/test/fixtures/dynamic-route-collision/src/pages/tags/index.astro @@ -0,0 +1,8 @@ + + + Static Tags Index + + +

Static Tags Index

+ + diff --git a/packages/astro/test/fixtures/dynamic-route-collision/src/pages/who.astro b/packages/astro/test/fixtures/dynamic-route-collision/src/pages/who.astro new file mode 100644 index 000000000000..00d12c59deda --- /dev/null +++ b/packages/astro/test/fixtures/dynamic-route-collision/src/pages/who.astro @@ -0,0 +1,8 @@ + + + Static Who We Are + + +

Static Who We Are

+ + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index edc246409e3c..c3b33f704207 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2732,6 +2732,12 @@ importers: specifier: workspace:* version: link:../../.. + packages/astro/test/fixtures/dynamic-route-collision: + dependencies: + astro: + specifier: workspace:* + version: link:../../.. + packages/astro/test/fixtures/entry-file-names: dependencies: '@astrojs/preact':