remix-run/remix · error · TypeError

mounts keys must be URL pathnames without query strings, fra

Error message

mounts keys must be URL pathnames without query strings, fragments, or encoded dot segments. Received "${urlRoot}".

What it means

mounts keys are URL pathnames only: no query strings, no fragments, and no encoded dot segments (like %2e%2e) that would change the path structure after normalization. normalizeMountUrlRoot parses the key and compares path segment counts before/after normalization to catch encoded traversal.

Source

Thrown at packages/assets/src/lib/routes.ts:132

  return {
    fileRoot: resolveMountFileRoot(options.rootDir, fileRoot),
    fileRootValue: fileRoot,
    urlRoot: joinUrlPath(normalizeMountUrlRoot(options.basePath), normalizeMountUrlRoot(urlRoot)),
    urlRootKey: urlRoot,
  }
}

function normalizeMountUrlRoot(urlRoot: string): string {
  let normalizedRoot = normalizePathname(urlRoot).replace(/\/+$/, '') || '/'
  let url = new URL(normalizedRoot, 'http://remix.run')

  if (
    url.search !== '' ||
    url.hash !== '' ||
    getUrlPathSegmentCount(url.pathname) !== getUrlPathSegmentCount(normalizedRoot)
  ) {
    throw new TypeError(
      `mounts keys must be URL pathnames without query strings, fragments, or encoded dot segments. Received "${urlRoot}".`,
    )
  }

  return url.pathname.replace(/\/+$/, '') || '/'
}

function resolveMountFileRoot(rootDir: string, fileRoot: string): string {
  let resolvedRoot = resolveFilePath(rootDir, fileRoot)

  try {
    resolvedRoot = normalizeFilePath(fs.realpathSync(resolvedRoot))
  } catch (error) {
    if (!isUnresolvedPathError(error, resolvedRoot)) throw error
  }

  return resolvedRoot.replace(/\/+$/, '') || '/'
}

View on GitHub (pinned to 9696913134)

Solutions

  1. Use a plain pathname key: '/assets'
  2. Strip query and hash when deriving keys from URLs: new URL(u).pathname
  3. Reject keys containing '%' encodings of '.' or '/' segments before passing

Example fix

// before
mounts: { '/assets?version=2': 'public/assets' }
// after
mounts: { '/assets': 'public/assets' }
Defensive patterns

Strategy: validation

Validate before calling

for (const key of Object.keys(mounts)) {
  const u = new URL(`http://x${key}`)
  if (u.search || u.hash || /%2e/i.test(key)) throw new Error('bad mounts key: ' + key)
}

Type guard

function isValidMountKey(key: string): boolean {
  const u = new URL(`http://x${key}`)
  return u.search === '' && u.hash === '' && !/%2e/i.test(key)
}

Prevention

When it happens

Trigger: Passing mounts keys like '/assets?v=1', '/assets#frag', or '/%2e%2e/assets' to configMounts.

Common situations: Copying URL patterns from link hrefs (including query/hash) into mounts config; attempting path traversal via encoded segments; keys assembled from user input without sanitization.

Related errors


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