{"record":{"id":"c15bf8c363cf7b90","repo":"withastro/astro","slug":"invalid-route-file-parameter-name-must-match-c15bf8","errorCode":null,"errorMessage":"Invalid route ${file} — parameter name must match /^[a-zA-Z0-9_$]+$/","messagePattern":"Invalid route (.+?) — parameter name must match /\\^\\[a-zA-Z0-9_\\$\\]\\+\\$/","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/integrations/vercel/src/lib/redirects.ts","lineNumber":22,"sourceCode":"import type { AstroConfig, IntegrationResolvedRoute, RoutePart } from 'astro';\n\nconst pathJoin = nodePath.posix.join;\n\n// Copied from astro/packages/astro/src/core/routing/manifest/create.ts\n// Disable eslint as we're not sure how to improve this regex yet\n// eslint-disable-next-line regexp/no-super-linear-backtracking\nconst ROUTE_DYNAMIC_SPLIT = /\\[(.+?\\(.+?\\)|.+?)\\]/;\nconst ROUTE_SPREAD = /^\\.{3}.+$/;\nfunction getParts(part: string, file: string) {\n\tconst result: RoutePart[] = [];\n\tpart.split(ROUTE_DYNAMIC_SPLIT).map((str, i) => {\n\t\tif (!str) return;\n\t\tconst dynamic = i % 2 === 1;\n\n\t\tconst [, content] = dynamic ? /([^(]+)$/.exec(str) || [null, null] : [null, str];\n\n\t\tif (!content || (dynamic && !/^(?:\\.\\.\\.)?[\\w$]+$/.test(content))) {\n\t\t\tthrow new Error(`Invalid route ${file} — parameter name must match /^[a-zA-Z0-9_$]+$/`);\n\t\t}\n\n\t\tresult.push({\n\t\t\tcontent,\n\t\t\tdynamic,\n\t\t\tspread: dynamic && ROUTE_SPREAD.test(content),\n\t\t});\n\t});\n\n\treturn result;\n}\n/**\n * Convert Astro routes into Vercel path-to-regexp syntax, which are the input for getTransformedRoutes\n */\nfunction getMatchPattern(segments: RoutePart[][]) {\n\treturn segments\n\t\t.map((segment) => {\n\t\t\treturn segment","sourceCodeStart":4,"sourceCodeEnd":40,"githubUrl":"https://github.com/withastro/astro/blob/d081033d5fe8e8a68c4bbbad4af9d2deb9c74bca/packages/integrations/vercel/src/lib/redirects.ts#L4-L40","documentation":"Thrown by Vercel's `getParts()` route parser when a dynamic route segment's parameter name does not match `/^[a-zA-Z0-9_$]+$/`. Vercel's redirect/route transform needs valid JS-identifier-like names for path parameters; invalid characters make the route unrepresentable.","triggerScenarios":"A file/folder like `src/pages/[user-id].astro` (hyphen), `[user.name].astro` (dot), `[user@name].astro`, or a rest spread `[...paths with space]`. Any dynamic param whose content after stripping optional regex group syntax fails `/^(?:\\.\\.\\.)?[\\w$]+$/`.","commonSituations":"Naming route params with hyphens (common mistake from URL conventions). Using dots or special chars. Copying a URL slug pattern directly into a filename.","solutions":["Rename the param to use only letters, digits, underscore, or `$`: `[userId].astro` or `[user_id].astro`.","For rest/spread params, keep `[...paramName]` with a valid identifier.","Avoid hyphens and dots in the bracketed param name; use camelCase or underscores instead."],"exampleFix":"// before: src/pages/blog/[post-slug].astro\n// after: src/pages/blog/[postSlug].astro","handlingStrategy":"validation","validationCode":"function isValidParamName(name: string): boolean {\n  return /^[a-zA-Z0-9_$]+$/.test(name);\n}\n// before shipping routes, scan filenames:\nfor (const f of pageFiles) {\n  for (const [, param] of f.matchAll(/\\[(.+?)\\]/g)) {\n    if (!isValidParamName(param)) throw new Error(`Invalid param '${param}' in ${f}`);\n  }\n}","typeGuard":"function isValidRouteParam(name: string): boolean {\n  return /^(?:\\.\\.\\.)?[a-zA-Z0-9_$]+$/.test(name);\n}","tryCatchPattern":null,"preventionTips":["Use camelCase or underscores for dynamic route params, never hyphens/dots.","Add a CI lint that scans `src/pages/**/[*]` filenames.","Document allowed param name characters for contributors."],"tags":["vercel","routing","dynamic-routes","filename"],"backgroundTag":null,"analyzedSha":"d081033d5fe8e8a68c4bbbad4af9d2deb9c74bca","analyzedAt":"2026-08-12T13:37:29.035Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}