withastro/astro · error · Error
Invalid route — parameter name must match /^[a-zA-Z0-9_$]+$/
Error message
Invalid route ${file} — parameter name must match /^[a-zA-Z0-9_$]+$/ What it means
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.
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
Example fix
// before: src/pages/blog/[post-slug].astro // after: src/pages/blog/[slug].astro // the route still matches /blog/my-post-slug
Defensive patterns
Strategy: validation
Validate before calling
const PARAM_RE = /^(?:\.\.\.)?[a-zA-Z0-9_$]+$/;
function hasValidParams(segments: string[]): boolean {
return segments.every((s) => {
const m = /^\[(.+)\]$/.exec(s);
return !m || PARAM_RE.test(m[1].split('=')[0]);
});
} Prevention
- 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/
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Invalid route — brackets are unbalanced
- Invalid route — parameters must be separated
- Error generating redirects
- Error generating routes
- Invalid route — rest parameter must be a standalone segment
AI-assisted analysis of withastro/astro@52e6c34790 (2026-08-18).
Data as JSON: /api/errors/c15bf8c363cf7b90.
Report an issue: GitHub.
Appendix: source
Thrown at packages/integrations/vercel/src/lib/redirects.ts:22
import type { AstroConfig, IntegrationResolvedRoute, RoutePart } from 'astro';
const pathJoin = nodePath.posix.join;
// Copied from astro/packages/astro/src/core/routing/manifest/create.ts
// Disable eslint as we're not sure how to improve this regex yet
// eslint-disable-next-line regexp/no-super-linear-backtracking
const ROUTE_DYNAMIC_SPLIT = /\[(.+?\(.+?\)|.+?)\]/;
const ROUTE_SPREAD = /^\.{3}.+$/;
function getParts(part: string, file: string) {
const result: RoutePart[] = [];
part.split(ROUTE_DYNAMIC_SPLIT).map((str, i) => {
if (!str) return;
const dynamic = i % 2 === 1;
const [, content] = dynamic ? /([^(]+)$/.exec(str) || [null, null] : [null, str];
if (!content || (dynamic && !/^(?:\.\.\.)?[\w$]+$/.test(content))) {
throw new Error(`Invalid route ${file} — parameter name must match /^[a-zA-Z0-9_$]+$/`);
}
result.push({
content,
dynamic,
spread: dynamic && ROUTE_SPREAD.test(content),
});
});
return result;
}
/**
* Convert Astro routes into Vercel path-to-regexp syntax, which are the input for getTransformedRoutes
*/
function getMatchPattern(segments: RoutePart[][]) {
return segments
.map((segment) => {
return segmentView on GitHub (pinned to 52e6c34790)