{"record":{"id":"48f8edf7cd61b9be","repo":"remix-run/remix","slug":"invalid-param-delimiter","errorCode":null,"errorMessage":"invalid param delimiter","messagePattern":"invalid param delimiter","errorType":"validation","errorClass":"ParseError","httpStatus":null,"severity":"error","filePath":"packages/route-pattern/src/lib/route-pattern/parse.ts","lineNumber":174,"sourceCode":"    for (let next of nextConsumingTokenIndices(tokens, optionals, index + 1)) {\n      if (next === tokens.length) continue\n      let nextToken = tokens[next]\n\n      if (token.type === '*') {\n        if (nextToken.type === '*') {\n          throw new ParseError('adjacent wildcards', source, tokenIndices[next])\n        }\n        continue\n      }\n\n      if (\n        nextToken.type === 'separator' ||\n        nextToken.type === '*' ||\n        (nextToken.type === 'text' && nextToken.text.startsWith('.'))\n      ) {\n        continue\n      }\n      throw new ParseError('invalid param delimiter', source, tokenIndices[next])\n    }\n  }\n}\n\nfunction nextConsumingTokenIndices(\n  tokens: ReadonlyArray<PartPatternToken>,\n  optionals: ReadonlyMap<number, number>,\n  start: number,\n): Set<number> {\n  let result = new Set<number>()\n  let pending = [start]\n  let visited = new Set<number>()\n\n  while (pending.length > 0) {\n    let index = pending.pop()\n    if (index === undefined) break\n    if (visited.has(index)) continue\n    visited.add(index)","sourceCodeStart":156,"sourceCodeEnd":192,"githubUrl":"https://github.com/remix-run/remix/blob/9696913134be3a4423513d2775f7b31d6917c049/packages/route-pattern/src/lib/route-pattern/parse.ts#L156-L192","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nlet pattern = parseRoutePattern('/:lang-:region/posts')\n\n// after\nlet pattern = parseRoutePattern('/:locale/posts') // split locale yourself, or use separate segments","handlingStrategy":"validation","validationCode":"// reject glued params like /:a-:b\nif (/:[\\w]+[^\\w/\\s].[\\w]*:/.test(source)) { /* review delimiters */ }","typeGuard":"null","tryCatchPattern":"try { parseRoutePattern(source) } catch (e) { if (e instanceof ParseError && e.message === 'invalid param delimiter') { /* restructure segment at e.position */ } }","preventionTips":["One param per segment unless using dot-extension delimiters","Split composite params after matching instead"],"tags":["route-pattern","parse","params","syntax"],"backgroundTag":"invalid-route-pattern-syntax","analyzedSha":"9696913134be3a4423513d2775f7b31d6917c049","analyzedAt":"2026-08-27T19:55:01.024Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}