withastro/astro · error · Error
Parameter name must match /^[a-zA-Z0-9_$]+$/
Error message
Parameter name must match /^[a-zA-Z0-9_$]+$/
What it means
The Cloudflare adapter generates `_worker.js`/routes.json by converting Astro's file-based routes into URL patterns. Every dynamic segment between brackets must be a plain identifier (`[name]`) or a spread (`[...slug]`); the generator strips any regex suffix and then requires the remainder to match `^(?:\.\.\.)?[\w$]+$`. Anything else — regex route params, special characters, invalid identifiers — throws this validation error.
Solutions
- Rename the segment to a plain identifier: `[id(\d+)].astro` → `[id].astro`.
- Use `[...slug].astro` only for catch-all/rest segments.
- Move format constraints (numeric-only, slug patterns) into the page's `getStaticPaths`/param validation or middleware instead of the filename.
- Run a quick scan of src/pages for brackets containing anything beyond letters, digits, underscore, dollar, or a leading `...`.
Example fix
// before — src/pages/[id(\d+)].astro
// after — src/pages/[id].astro (validate inside getStaticPaths)
export async function getStaticPaths() {
return [1, 2, 3].map((id) => ({ params: { id: String(id) } }));
} Defensive patterns
Strategy: validation
Validate before calling
// Fail fast in CI before the adapter does: scan src/pages for invalid dynamic segments
import { globSync } from 'glob';
import path from 'node:path';
const valid = /^(?:\.\.\.)?[A-Za-z0-9_$]+$/;
for (const f of globSync('src/pages/**/*.{astro,ts,js,md}')) {
for (const seg of path.parse(f).dir.split(path.sep).concat(path.basename(f, path.extname(f)))) {
const m = seg.match(/^\[(.+)\]$/);
if (m && !valid.test(m[1].replace(/\([^)]*\)$/g, ''))) {
throw new Error(`Invalid route parameter "${m[1]}" in ${f}`);
}
}
} Prevention
- Keep bracketed route segments to plain identifiers and `[...spread]` only.
- Put format constraints in getStaticPaths or middleware, never in filenames.
- Add the filename lint to CI so bad segments fail before the Cloudflare build.
When it happens
Trigger: A page file like `src/pages/[id(\d+)].astro` or `src/pages/[user-id!].astro` (content with characters outside `[a-zA-Z0-9_$]` after spread/regex stripping) while the cloudflare adapter generates its routes manifest during build/dev.
Common situations: Porting projects that relied on regex route parameters (removed from Astro core) into a Cloudflare-deployed app; typos or URL-unsafe characters inside bracket segments; copying Next.js-style route filenames.
Related errors
- Error generating redirects
- Error generating routes
- Failed to get static images from the Cloudflare prerender…
- Failed to get static paths from the Cloudflare prerender…
- Failed to prerender
AI-assisted analysis of withastro/astro@52e6c34790 (2026-08-18).
Data as JSON: /api/errors/342d4318dc92d448.
Report an issue: GitHub.
Appendix: source
Thrown at packages/integrations/cloudflare/src/utils/generate-routes-json.ts:18
import type { RoutePart } from 'astro';
// QUESTION could be removed when we expose it from core https://discord.com/channels/830184174198718474/1471406261294727254
// Copied from https://github.com/withastro/astro/blob/3776ecf0aa9e08a992d3ae76e90682fd04093721/packages/astro/src/core/routing/manifest/create.ts#L45-L70
// 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}.+$/;
export function getParts(part: 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('Parameter name must match /^[a-zA-Z0-9_$]+$/');
}
result.push({
content,
dynamic,
spread: dynamic && ROUTE_SPREAD.test(content),
});
});
return result;
}
View on GitHub (pinned to 52e6c34790)