Skip to content

Commit

Permalink
Enforcing routing priority during production builds (#3407)
Browse files Browse the repository at this point in the history
* WIP: have a few test failures to track down

* WIP: still a few failures to fix

* WIP: fixes the issue of dynamic routes stepping on static routes

* Resolve route priority before building routes for `getStaticPaths()`

* chore: adding comments explaining why this filter is needed

* chore: adding changeset

* got too fancy with the test suite, these routes weren't valid

* simplifying the test cases

* TEMP: is this test breaking my CI run?

* Revert "TEMP: is this test breaking my CI run?"

This reverts commit 291af2a.

* slots-preact didn't list @astrojs/preact as a dep

* reverting copy/paste error
  • Loading branch information
Tony Sullivan committed May 20, 2022
1 parent 60d7164 commit 6373508
Show file tree
Hide file tree
Showing 13 changed files with 406 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .changeset/blue-buckets-attack.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Adds a check during build to make sure routing priority is always enforced in the final dist output
27 changes: 26 additions & 1 deletion packages/astro/src/core/build/page-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import { debug } from '../logger/core.js';
import { preload as ssrPreload } from '../render/dev/index.js';
import { generateRssFunction } from '../render/rss.js';
import { callGetStaticPaths, RouteCache, RouteCacheEntry } from '../render/route-cache.js';
import { removeTrailingForwardSlash } from '../path.js';
import { isBuildingToSSR } from '../util.js';
import { matchRoute } from '../routing/match.js';

export interface CollectPagesDataOptions {
astroConfig: AstroConfig;
Expand All @@ -35,6 +37,7 @@ export async function collectPagesData(

const assets: Record<string, string> = {};
const allPages: AllPagesData = {};
const builtPaths = new Set<string>();

const buildMode = isBuildingToSSR(astroConfig) ? 'ssr' : 'static';

Expand All @@ -60,6 +63,7 @@ export async function collectPagesData(
);
clearInterval(routeCollectionLogTimeout);
}, 10000);
builtPaths.add(route.pathname);
allPages[route.component] = {
component: route.component,
route,
Expand Down Expand Up @@ -138,7 +142,28 @@ export async function collectPagesData(
}
const finalPaths = result.staticPaths
.map((staticPath) => staticPath.params && route.generate(staticPath.params))
.filter(Boolean);
.filter((staticPath) => {
// Remove empty or undefined paths
if (!staticPath) {
return false;
}

// The path hasn't been built yet, include it
if (!builtPaths.has(removeTrailingForwardSlash(staticPath))) {
return true;
}

// The path was already built once. Check the manifest to see if
// this route takes priority for the final URL.
// NOTE: The same URL may match multiple routes in the manifest.
// Routing priority needs to be verified here for any duplicate
// paths to ensure routing priority rules are enforced in the final build.
const matchedRoute = matchRoute(staticPath, manifest);
return matchedRoute === route;
});

finalPaths.map((staticPath) => builtPaths.add(removeTrailingForwardSlash(staticPath)));

allPages[route.component] = {
component: route.component,
route,
Expand Down
11 changes: 4 additions & 7 deletions packages/astro/src/vite-plugin-astro-server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,13 +199,10 @@ async function handleRequest(
const devRoot = site ? site.pathname : '/';
const origin = `${viteServer.config.server.https ? 'https' : 'http'}:https://${req.headers.host}`;
const buildingToSSR = isBuildingToSSR(config);
// When file-based build format is used, pages will be built to `/blog.html`
// rather than `/blog/index.html`. The dev server should handle this as well
// to match production deployments.
const url =
config.build.format === 'file'
? new URL(origin + req.url?.replace(/(index)?\.html$/, ''))
: new URL(origin + req.url);
// Ignore `.html` extensions and `index.html` in request URLS to ensure that
// routing behavior matches production builds. This supports both file and directory
// build formats, and is necessary based on how the manifest tracks build targets.
const url = new URL(origin + req.url?.replace(/(index)?\.html$/, ''));
const pathname = decodeURI(url.pathname);
const rootRelativeUrl = pathname.substring(devRoot.length - 1);
if (!buildingToSSR) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { defineConfig } from 'astro/config';

// https://astro.build/config
export default defineConfig({});
8 changes: 8 additions & 0 deletions packages/astro/test/fixtures/routing-priority/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "@test/routing-priority",
"version": "0.0.0",
"private": true,
"dependencies": {
"astro": "workspace:*"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
export async function getStaticPaths() {
return [
{ params: { lang: 'de', catchall: '1/2' } },
{ params: { lang: 'en', catchall: '1/2' } }
]
}
---

<html lang="en">

<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Routing</title>
</head>

<body>
<h1>[lang]/[...catchall].astro</h1>
<p>{Astro.params.lang} | {Astro.params.catchall}</p>
</body>

</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
export async function getStaticPaths() {
return [
{ params: { lang: 'de' } }, // always shadowed by /de/index.astro
{ params: { lang: 'en' } }
];
}
---

<html lang="en">

<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Routing</title>
</head>

<body>
<h1>[lang]/index.astro</h1>
<p>{Astro.params.lang}</p>
</body>

</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
---
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Routing</title>
</head>
<body>
<h1>de/index.astro</h1>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
---
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Routing</title>
</head>
<body>
<h1>index.astro</h1>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
export async function getStaticPaths() {
return [
{
params: { slug: "1/2" },
}
]
}
---

<html lang="en">

<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Routing</title>
</head>

<body>
<h1>posts/[...slug].astro</h1>
<p>{Astro.params.slug}</p>
</body>

</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
export async function getStaticPaths() {
return [
{ params: { pid: 'post-1' } },
{ params: { pid: 'post-2' } }
]
}
---

<html lang="en">

<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Routing</title>
</head>

<body>
<h1>posts/[pid].astro</h1>
<p>{Astro.params.pid}</p>
</body>

</html>
Loading

0 comments on commit 6373508

Please sign in to comment.