remix-run/remix · error · ParseError
empty optional
Error message
empty optional
What it means
An optional group in the pattern contains no consuming tokens at all — e.g. '()' — making it meaningless and a likely authoring mistake. After parsing, if any optional group was recorded as empty, parsePart throws 'empty optional' at the group's source position.
Source
Thrown at packages/route-pattern/src/lib/route-pattern/parse.ts:138
if (char === '\\') {
if (i + 1 === span[1]) {
throw new ParseError('dangling escape', source, i)
}
let text = source.slice(i + 1, i + 2)
appendText(text)
i += 2
continue
}
appendText(char)
i += 1
}
let unmatchedOptional = optionalStack.at(-1)
if (unmatchedOptional !== undefined) {
throw new ParseError('unmatched (', source, unmatchedOptional.sourceIndex)
}
if (emptyOptionals.length > 0) {
throw new ParseError('empty optional', source, emptyOptionals[0])
}
validateTokenGrammar(source, tokens, optionals, tokenIndices)
validateOptionalCaptureSchemas(source, tokens, optionals, tokenIndices)
return { tokens, optionals, type: options.type }
}
function validateTokenGrammar(
source: string,
tokens: ReadonlyArray<PartPatternToken>,
optionals: ReadonlyMap<number, number>,
tokenIndices: ReadonlyArray<number>,
): void {
for (let [index, token] of tokens.entries()) {
if (token.type !== ':' && token.type !== '*') continue
for (let next of nextConsumingTokenIndices(tokens, optionals, index + 1)) {View on GitHub (pinned to 9696913134)
Solutions
- Remove the empty '()' group
- If the group was meant to hold an optional param, put the param inside: '(/:page)'
- Fix the generator so it omits the whole group when its content is empty
Example fix
// before
let pattern = parseRoutePattern(`/users/:id${opt}`) // opt === ''
// after
let pattern = parseRoutePattern(opt ? `/users/:id(${opt})` : '/users/:id') Defensive patterns
Strategy: validation
Validate before calling
if (/\(\)/.test(source) || /\(\(\)\)/.test(source)) throw new Error('Empty optional group') Type guard
null
Try / catch
try { parseRoutePattern(source) } catch (e) { if (e instanceof ParseError && e.message === 'empty optional') { /* remove the () at e.position */ } } Prevention
- When templating optionals, omit the whole group when content is empty
When it happens
Trigger: Calling parseRoutePattern with a source containing '()' or '(())' anywhere in a part, including groups whose only content is another empty optional.
Common situations: Template code that wraps an optional segment when the segment string is empty; cleanup scripts stripping params but leaving the parens; copy-paste leftovers.
Related errors
AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27).
Data as JSON: /api/errors/7d3985e4683836a2.
Report an issue: GitHub.