{"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/52e6c34790cc8ac4e69e6135ace06049867e5c4a/packages/integrations/vercel/src/lib/redirects.ts#L4-L40","documentation":"The Vercel adapter's redirects/route parser (`getParts` in lib/redirects.ts) splits each path segment on dynamic brackets and requires every dynamic parameter's name to match /^(?:[\\.\\.\\.]?[\\w$]+$)/ — i.e. letters, digits, underscore, dollar only, optionally prefixed with `...` for spread params. When a bracketed segment contains anything else (dashes, spaces, unicode, stray regex fragments), the file path is rejected with this error.","triggerScenarios":"Creating a page like `src/pages/[foo-bar].astro`, `src/pages/products/[itemId!].astro`, or a spread like `src/pages/[...path slug].astro`. Also triggered by custom-regex segments whose leftover name is empty or invalid after the `([^(]+)$` extraction.","commonSituations":"Porting URLs that used dashes (kebab-case slugs) into dynamic params, generating pages with a script that embeds punctuation in filenames, or copying Next.js-style `[...slug].tsx` patterns with slight modifications.","solutions":["Rename the file/folder so the bracketed param uses only [a-zA-Z0-9_$]: `[slug]` instead of `[post-slug]`","For spread params use exactly `[...name]` (three dots then a valid identifier)","For custom regex routing, keep the name before the `=` valid: `[lang=([a-z]{2})]`","Remember the dash belongs in the URL value, not the param name — the param can still match kebab-case slugs at runtime"],"exampleFix":"// before: src/pages/blog/[post-slug].astro\n\n// after: src/pages/blog/[slug].astro\n// the route still matches /blog/my-post-slug","handlingStrategy":"validation","validationCode":"const PARAM_RE = /^(?:\\.\\.\\.)?[a-zA-Z0-9_$]+$/;\nfunction hasValidParams(segments: string[]): boolean {\n  return segments.every((s) => {\n    const m = /^\\[(.+)\\]$/.exec(s);\n    return !m || PARAM_RE.test(m[1].split('=')[0]);\n  });\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use only [a-zA-Z0-9_$] inside route brackets; kebab-case belongs in URL values, not param names","Write spread params exactly as [...name]","Add a lint/CI check or code review rule for new files under src/pages/"],"tags":["vercel","routing","file-naming","dynamic-routes"],"backgroundTag":"invalid-route-parameter","analyzedSha":"52e6c34790cc8ac4e69e6135ace06049867e5c4a","analyzedAt":"2026-08-18T18:48:03.901Z","contentChangedAt":"2026-08-18T18:48:03.901Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}