Skip to content

Commit e3271f8

Browse files
authored
Better dynamic route regex error (#7339)
* feat: add helpful invalid route seg error * chore: add errors-data entry * refactor: add hint for non-spread errors * chore: add `undefined` to common cases * nit: if the param contains slashes * chore: changeset * chore: it's trivial my dear watson
1 parent 52f0480 commit e3271f8

3 files changed

Lines changed: 61 additions & 2 deletions

File tree

.changeset/kind-cycles-smile.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'astro': patch
3+
---
4+
5+
Add readable error message for invalid dynamic routes.

packages/astro/src/core/build/generate.ts

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type {
1010
ComponentInstance,
1111
EndpointHandler,
1212
EndpointOutput,
13+
GetStaticPathsItem,
1314
ImageTransform,
1415
MiddlewareResponseHandler,
1516
RouteData,
@@ -36,7 +37,7 @@ import { runHookBuildGenerated } from '../../integrations/index.js';
3637
import { isServerLikeOutput } from '../../prerender/utils.js';
3738
import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../vite-plugin-scripts/index.js';
3839
import { callEndpoint, createAPIContext, throwIfRedirectNotAllowed } from '../endpoint/index.js';
39-
import { AstroError } from '../errors/index.js';
40+
import { AstroError, AstroErrorData } from '../errors/index.js';
4041
import { debug, info } from '../logger/core.js';
4142
import { callMiddleware } from '../middleware/callMiddleware.js';
4243
import {
@@ -306,7 +307,16 @@ async function getPathsForRoute(
306307
opts.routeCache.set(route, result);
307308

308309
paths = result.staticPaths
309-
.map((staticPath) => staticPath.params && route.generate(staticPath.params))
310+
.map((staticPath) => {
311+
try {
312+
return route.generate(staticPath.params);
313+
} catch (e) {
314+
if (e instanceof TypeError) {
315+
throw getInvalidRouteSegmentError(e, route, staticPath);
316+
}
317+
throw e;
318+
}
319+
})
310320
.filter((staticPath) => {
311321
// The path hasn't been built yet, include it
312322
if (!builtPaths.has(removeTrailingForwardSlash(staticPath))) {
@@ -331,6 +341,37 @@ async function getPathsForRoute(
331341
return paths;
332342
}
333343

344+
function getInvalidRouteSegmentError(
345+
e: TypeError,
346+
route: RouteData,
347+
staticPath: GetStaticPathsItem
348+
): AstroError {
349+
const invalidParam = e.message.match(/^Expected "([^"]+)"/)?.[1];
350+
const received = invalidParam ? staticPath.params[invalidParam] : undefined;
351+
let hint =
352+
'Learn about dynamic routes at https://docs.astro.build/en/core-concepts/routing/#dynamic-routes';
353+
if (invalidParam && typeof received === 'string') {
354+
const matchingSegment = route.segments.find(
355+
(segment) => segment[0]?.content === invalidParam
356+
)?.[0];
357+
const mightBeMissingSpread = matchingSegment?.dynamic && !matchingSegment?.spread;
358+
if (mightBeMissingSpread) {
359+
hint = `If the param contains slashes, try using a rest parameter: **[...${invalidParam}]**. Learn more at https://docs.astro.build/en/core-concepts/routing/#dynamic-routes`;
360+
}
361+
}
362+
return new AstroError({
363+
...AstroErrorData.InvalidDynamicRoute,
364+
message: invalidParam
365+
? AstroErrorData.InvalidDynamicRoute.message(
366+
route.route,
367+
JSON.stringify(invalidParam),
368+
JSON.stringify(received)
369+
)
370+
: `Generated path for ${route.route} is invalid.`,
371+
hint,
372+
});
373+
}
374+
334375
interface GeneratePathOptions {
335376
pageData: PageBuildData;
336377
internals: BuildInternals;

packages/astro/src/core/errors/errors-data.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -760,6 +760,19 @@ See https://docs.astro.build/en/guides/server-side-rendering/ for more informati
760760
title: 'A redirect must be given a location with the `Location` header.',
761761
code: 3037,
762762
},
763+
/**
764+
* @docs
765+
* @see
766+
* - [Dynamic routes](https://docs.astro.build/en/core-concepts/routing/#dynamic-routes)
767+
* @description
768+
* A dynamic route param is invalid. This is often caused by an `undefined` parameter or a missing [rest parameter](https://docs.astro.build/en/core-concepts/routing/#rest-parameters).
769+
*/
770+
InvalidDynamicRoute: {
771+
title: 'Invalid dynamic route.',
772+
code: 3038,
773+
message: (route: string, invalidParam: string, received: string) =>
774+
`The ${invalidParam} param for route ${route} is invalid. Received **${received}**.`,
775+
},
763776
// No headings here, that way Vite errors are merged with Astro ones in the docs, which makes more sense to users.
764777
// Vite Errors - 4xxx
765778
/**

0 commit comments

Comments
 (0)