0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2024-12-30 22:03:56 -05:00

Fix dynamic prerender conflict (#10298)

* Reproduce issues

* Handle inconsistency between static, dynamic and rest routes

* Add extra test cases

* Add changeset

* Revert unrelated changes

* Update lockfile
This commit is contained in:
Luiz Ferraz 2024-03-04 13:40:32 -03:00 committed by GitHub
parent c99bbd09af
commit 819d20a89c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 322 additions and 2 deletions

View file

@ -0,0 +1,5 @@
---
"astro": patch
---
Fix an incorrect conflict resolution between pages generated from static routes and rest parameters

View file

@ -326,7 +326,7 @@ async function getPathsForRoute(
let paths: Array<string> = []; let paths: Array<string> = [];
if (route.pathname) { if (route.pathname) {
paths.push(route.pathname); paths.push(route.pathname);
builtPaths.add(route.pathname); builtPaths.add(removeTrailingForwardSlash(route.pathname));
} else { } else {
const staticPaths = await callGetStaticPaths({ const staticPaths = await callGetStaticPaths({
mod, mod,

View file

@ -37,5 +37,13 @@ export function getRouteGenerator(
trailing = '/'; trailing = '/';
} }
const toPath = compile(template + 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 || '/';
};
} }

View file

@ -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');
});
});

View file

@ -0,0 +1,8 @@
{
"name": "@test/dynamic-route-collision",
"version": "0.0.0",
"private": true,
"dependencies": {
"astro": "workspace:*"
}
}

View file

@ -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;
---
<html>
<head>
<title>{title}</title>
</head>
<body>
<h1>{title}</h1>
</body>
</html>

View file

@ -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;
---
<html>
<head>
<title>{title}</title>
</head>
<body>
<h1>{title}</h1>
</body>
</html>

View file

@ -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;
---
<html>
<head>
<title>{title}</title>
</head>
<body>
<h1>{title}</h1>
</body>
</html>

View file

@ -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;
---
<html>
<head>
<title>{title}</title>
</head>
<body>
<h1>{title}</h1>
</body>
</html>

View file

@ -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;
---
<html>
<head>
<title>{title}</title>
</head>
<body>
<h1>{title}</h1>
</body>
</html>

View file

@ -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;
---
<html>
<head>
<title>{title}</title>
</head>
<body>
<h1>{title}</h1>
</body>
</html>

View file

@ -0,0 +1,8 @@
<html>
<head>
<title>Static About Page</title>
</head>
<body>
<h1>Static About</h1>
</body>
</html>

View file

@ -0,0 +1,8 @@
<html>
<head>
<title>Static Index Page</title>
</head>
<body>
<h1>Static Index</h1>
</body>
</html>

View file

@ -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;
---
<html>
<head>
<title>{title}</title>
</head>
<body>
<h1>{title}</h1>
</body>
</html>

View file

@ -0,0 +1,8 @@
<html>
<head>
<title>Static Tags Index</title>
</head>
<body>
<h1>Static Tags Index</h1>
</body>
</html>

View file

@ -0,0 +1,8 @@
<html>
<head>
<title>Static Who We Are</title>
</head>
<body>
<h1>Static Who We Are</h1>
</body>
</html>

View file

@ -2732,6 +2732,12 @@ importers:
specifier: workspace:* specifier: workspace:*
version: link:../../.. version: link:../../..
packages/astro/test/fixtures/dynamic-route-collision:
dependencies:
astro:
specifier: workspace:*
version: link:../../..
packages/astro/test/fixtures/entry-file-names: packages/astro/test/fixtures/entry-file-names:
dependencies: dependencies:
'@astrojs/preact': '@astrojs/preact':