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
- Use a plain pathname key: '/assets'
- Strip query and hash when deriving keys from URLs: new URL(u).pathname
- 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
- Use pathname-only strings for mount keys
- Sanitize user-sourced keys through new URL(...).pathname
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
- mounts keys must not overlap. Received "${mount.urlRootKey}"
- mounts values must be relative to rootDir. Received "${fileR
- mounts values must not overlap. Received "${mount.fileRootVa
- Route module ${routesFile} must export a named "routes" valu
- Invalid route map value at "${location}". Expected a nested
AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27).
Data as JSON: /api/errors/801f1524f6ec098a.
Report an issue: GitHub.