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

`getParts` parses a single path segment of an Astro file-based route into dynamic/static parts. After splitting on `[...]`, the content captured inside a dynamic bracket must satisfy `/^(?:\.\.\.)?[\w$]+$/` — an optional leading spread (`...`) followed by word characters and `$`. Anything else (spaces, hyphens, unicode, dots other than the spread, regex like-parameters with invalid names) throws a plain `Error`.

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 d081033d5f)

Solutions

  1. Rename the dynamic segment file so the bracketed name matches `/^[a-zA-Z0-9_$]+$/` (e.g. `[user-id]` → `[userId]`).
  2. For spread/rest params use exactly `[...name]` with a valid identifier name.
  3. For regex params, ensure the leading name portion is a valid identifier (e.g. `[param]` not `[-param]`).
  4. Run `astro sync` / `astro build` after renaming to confirm `_routes.json` generation succeeds.

Example fix

// before
src/pages/blog/[post-id].astro

// after
src/pages/blog/[postId].astro
Defensive patterns

Strategy: validation

Validate before calling

const DYNAMIC_NAME = /^(?:\.\.\.)?[a-zA-Z0-9_$]+$/;
function isValidRouteName(segment: string): boolean {
  const match = segment.match(/\[([^\]]+)\]/);
  return !match || DYNAMIC_NAME.test(match[1]);
}
// before build:
if (!isValidRouteName('[user-id]')) throw new Error('bad route name');

Type guard

function isValidDynamicParam(content: string): boolean {
  return /^(?:\.\.\.)?[a-zA-Z0-9_$]+$/.test(content);
}

Try / catch

try {
  getParts(segment);
} catch (e) {
  throw new Error(`Invalid route segment "${segment}": dynamic param names must match /^[a-zA-Z0-9_$]+$/`);
}

Prevention

When it happens

Trigger: Calling `getParts(part)` (indirectly during `_routes.json` generation for the Cloudflare adapter) with a route segment whose bracketed name is malformed, e.g. `src/pages/[my-param].astro`, `src/pages/[foo.bar].astro`, `src/pages/[1num].astro`, or a regex param `[...]` whose captured name fails the word-char test.

Common situations: Naming a dynamic route param with a hyphen (`[user-id]`) or dot. Copying a route filename from another framework. Mixing rest-spread syntax incorrectly (`[...slug.extra]`). Building on Cloudflare triggers `generate-routes-json` which invokes this parser.

Related errors


AI-assisted analysis of withastro/astro@d081033d5f (2026-08-12). Data as JSON: /api/errors/342d4318dc92d448. Report an issue: GitHub.