remix-run/remix · error · ParseError
invalid param delimiter
Error message
invalid param delimiter
What it means
After a named param (`:id`) or wildcard, the next consuming token must act as a delimiter — a separator ('/'), another wildcard, or a dot-prefixed literal (like '.json'). If something else follows (a plain literal glued directly onto the param, e.g. '/users/:idx2' is fine as identifier, but '/users/:id-' style text without a valid delimiter), the boundary between the param and surrounding text is ambiguous and parsePart's grammar validation throws 'invalid param delimiter' at that token.
Source
Thrown at packages/route-pattern/src/lib/route-pattern/parse.ts:174
for (let next of nextConsumingTokenIndices(tokens, optionals, index + 1)) {
if (next === tokens.length) continue
let nextToken = tokens[next]
if (token.type === '*') {
if (nextToken.type === '*') {
throw new ParseError('adjacent wildcards', source, tokenIndices[next])
}
continue
}
if (
nextToken.type === 'separator' ||
nextToken.type === '*' ||
(nextToken.type === 'text' && nextToken.text.startsWith('.'))
) {
continue
}
throw new ParseError('invalid param delimiter', source, tokenIndices[next])
}
}
}
function nextConsumingTokenIndices(
tokens: ReadonlyArray<PartPatternToken>,
optionals: ReadonlyMap<number, number>,
start: number,
): Set<number> {
let result = new Set<number>()
let pending = [start]
let visited = new Set<number>()
while (pending.length > 0) {
let index = pending.pop()
if (index === undefined) break
if (visited.has(index)) continue
visited.add(index)View on GitHub (pinned to 9696913134)
Solutions
- Separate params with a proper delimiter: '/:a/:b' or use a literal dot '.ext' which is allowed
- Restructure '/:lang-:region' into separate segments or a single param you split yourself
- Keep extensions in the dot form: '/files/:name(.:ext)' style supported delimiters
Example fix
// before
let pattern = parseRoutePattern('/:lang-:region/posts')
// after
let pattern = parseRoutePattern('/:locale/posts') // split locale yourself, or use separate segments Defensive patterns
Strategy: validation
Validate before calling
// reject glued params like /:a-:b
if (/:[\w]+[^\w/\s].[\w]*:/.test(source)) { /* review delimiters */ } Type guard
null
Try / catch
try { parseRoutePattern(source) } catch (e) { if (e instanceof ParseError && e.message === 'invalid param delimiter') { /* restructure segment at e.position */ } } Prevention
- One param per segment unless using dot-extension delimiters
- Split composite params after matching instead
When it happens
Trigger: Calling parseRoutePattern with a pattern where a param is immediately followed by text that is not a legal delimiter, e.g. '/users/:idv2' is one identifier (fine), but '/res/:id@meta' or '/:lang-:region' where '-' glued text follows the param name boundary produces an invalid delimiter token.
Common situations: Trying to embed two params in one segment separated by arbitrary literals ('/:a-:b'); adding file extensions with characters other than a leading dot; localizing patterns with glued separators.
Related errors
AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27).
Data as JSON: /api/errors/48f8edf7cd61b9be.
Report an issue: GitHub.