remix-run/remix · error · ParseError

adjacent wildcards

Error message

adjacent wildcards

What it means

During grammar validation after tokenizing, two wildcard (`*`) tokens were found adjacent to each other with no separator between them (e.g. '/a/**/b' as two splats back-to-back). Adjacent wildcards are ambiguous to match and serialize, so a ParseError 'adjacent wildcards' is thrown at the second wildcard's index.

Source

Thrown at packages/route-pattern/src/lib/route-pattern/parse.ts:162

  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)) {
      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>,

View on GitHub (pinned to 9696913134)

Solutions

  1. Use a single wildcard: '/files/*/download' or '/files/*'
  2. If you need multi-segment matching, one splat already spans separators — use a named splat '*rest'
  3. Insert a literal or named param between the wildcards if both are genuinely needed

Example fix

// before
let pattern = parseRoutePattern('/files/*/*/download')

// after
let pattern = parseRoutePattern('/files/*rest/download')
Defensive patterns

Strategy: validation

Validate before calling

if (/\*(\([^)]*\))?\*/.test(source.replace(/\\./g, ''))) throw new Error('Adjacent wildcards')

Type guard

null

Try / catch

try { parseRoutePattern(source) } catch (e) { if (e instanceof ParseError && e.message === 'adjacent wildcards') { /* collapse to one splat */ } }

Prevention

When it happens

Trigger: Calling parseRoutePattern with a pattern containing two splat tokens next to each other or separated only by optionals that can collapse, such as '/files/*/*/download' or '/a/*/b(*)/c' where the optional resolves to adjacent wildcards.

Common situations: Copying glob syntax (`**`) from file matchers into route patterns; concatenating two catch-all fragments; refactoring nested routes into one pattern and stacking splats.

Related errors


AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27). Data as JSON: /api/errors/993af1138209b409. Report an issue: GitHub.