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

  1. Rename the segment to a plain identifier: `[id(\d+)].astro` → `[id].astro`.
  2. Use `[...slug].astro` only for catch-all/rest segments.
  3. Move format constraints (numeric-only, slug patterns) into the page's `getStaticPaths`/param validation or middleware instead of the filename.
  4. 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

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


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)