withastro/astro · error · Error

Invalid route — brackets are unbalanced

Error message

Invalid route ${file} — brackets are unbalanced

What it means

Dynamic-route params must be wrapped in matched [...] pairs. validateSegment counts opening and closing brackets with countOccurrences and throws when they differ, e.g. [id.astro (missing ]) or id]].astro (extra ]). An unbalanced pattern cannot be compiled into a route regex, and leaving the mistake in place would make every URL match (or fail to match) unpredictably.

Solutions

  1. Find the offending file (the error message names it in ${file}) and rename it so every '[' has a matching ']'.
  2. Double-check rest params specifically: [...slug].astro needs both the inner dots and both brackets.

Example fix

// before
src/pages/blog/[slug.astro      // 1 '[' vs 0 ']' -> unbalanced

// after
src/pages/blog/[slug].astro      // brackets balanced
Defensive patterns

Strategy: validation

Validate before calling

function isBalanced(segment: string): boolean {
  const open = [...segment].filter((c) => c === '[').length;
  const close = [...segment].filter((c) => c === ']').length;
  return open === close;
}

// before running astro dev/build, assert every pages/ filename passes isBalanced

Prevention

When it happens

Trigger: Any pages-directory filename or segment where the number of '[' characters is not equal to the number of ']' characters: src/pages/[slug.astro, src/pages/blog]].astro, src/pages/[...tags.astro.

Common situations: Typo while creating a dynamic route; editor auto-rename cutting a bracket; copying a route filename from docs and losing a character; shell escaping eating a bracket when creating files via CLI.

Related errors


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

Appendix: 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 52e6c34790)