withastro/astro · error · Error

Invalid route ${file} — brackets are unbalanced

Error message

Invalid route ${file} — brackets are unbalanced

What it means

Astro validates the bracket structure of every route segment at route-collection time (build and dev start). This error fires from validateSegment when a dynamic route filename contains an unequal count of '[' and ']', so a parameter name cannot be parsed. It is a build-time routing error, not a runtime one.

Source

Thrown at packages/astro/src/core/routing/segment.ts:8

export function validateSegment(segment: string, file = '') {
	if (!file) file = segment;

	if (segment.includes('][')) {
		throw new Error(`Invalid route ${file} \u2014 parameters must be separated`);
	}
	if (countOccurrences('[', segment) !== countOccurrences(']', segment)) {
		throw new Error(`Invalid route ${file} \u2014 brackets are unbalanced`);
	}
	if (
		(/.+\[\.\.\.[^\]]+\]/.test(segment) || /\[\.\.\.[^\]]+\].+/.test(segment)) &&
		file.endsWith('.astro')
	) {
		throw new Error(`Invalid route ${file} \u2014 rest parameter must be a standalone segment`);
	}
}

function countOccurrences(needle: string, haystack: string) {
	let count = 0;
	for (const hay of haystack) {
		if (hay === needle) count += 1;
	}
	return count;
}

View on GitHub (pinned to d081033d5f)

Solutions

  1. Rename the file so every '[' has a matching ']' wrapping a single parameter, e.g. [id].astro.
  2. Search the reported filename for stray brackets and remove any literal '[' or ']'.
  3. Run `astro dev` again to confirm validateSegment no longer throws.

Example fix

// before
src/pages/blog/[id.astro
// after
src/pages/blog/[id].astro
Defensive patterns

Strategy: validation

Validate before calling

function hasBalancedBrackets(name) {
  let depth = 0;
  for (const ch of name) {
    if (ch === '[') depth++;
    else if (ch === ']') depth--;
    if (depth < 0) return false;
  }
  return depth === 0;
}
for (const f of routeFiles) {
  if (!hasBalancedBrackets(f)) throw new Error(`Unbalanced brackets in ${f}`);
}

Type guard

const isValidSegmentName = (name: string): boolean =>
  (name.match(/\[/g)?.length ?? 0) === (name.match(/\]/g)?.length ?? 0) && !name.includes('][');

Prevention

When it happens

Trigger: A file under src/pages/ whose name has unmatched brackets, e.g. src/pages/blog/[id.astro (missing ']'), src/pages/[slug]].astro (extra ']'), or src/pages/post[id.astro. validateSegment is invoked per segment during route discovery.

Common situations: Renaming a dynamic route file and forgetting the closing bracket; copy-pasting a [param] token and dropping one bracket; accidentally using literal '[' or ']' in a static filename (Astro treats them as parameter delimiters).

Related errors


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