remix-run/remix · error · TypeError
${optionPath} must use "X", "X.Y", or "X.Y.Z" version format
Error message
${optionPath} must use "X", "X.Y", or "X.Y.Z" version format What it means
Thrown when a browser target version does not match the X, X.Y, or X.Y.Z numeric format. The assets normalizer parses browser versions into up to three numeric components, so values like 'v90', '90.x', 'latest', '90.1.1.1', or '90-beta' fail this regex.
Source
Thrown at packages/assets/src/lib/target.ts:178
if (!/^\d{4}$/.test(normalizedValue) || Number(normalizedValue) < 2015) {
throw new TypeError(`${optionPath} must use a four-digit year of 2015 or higher`)
}
return `es${normalizedValue}`
}
function normalizeBrowserTargetVersion(value: unknown, optionPath: string): AssetTargetVersion {
if (typeof value !== 'string') {
throw new TypeError(`${optionPath} must be a string`)
}
if (value.trim().length === 0) {
throw new TypeError(`${optionPath} must be a non-empty string`)
}
if (!/^\d+(\.\d+){0,2}$/.test(value)) {
throw new TypeError(`${optionPath} must use "X", "X.Y", or "X.Y.Z" version format`)
}
let segments = value.split('.').map(Number)
if (segments.some((segment) => segment > 255)) {
throw new TypeError(`${optionPath} must use version components between 0 and 255`)
}
return value as AssetTargetVersion
}
function toLightningCssTargetVersion(version: AssetTargetVersion): number {
let [major, minor = 0, patch = 0] = version.split('.').map(Number)
return major * 65536 + minor * 256 + patch
}
function isPlainObject(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}View on GitHub (pinned to 9696913134)
Solutions
- Strip prefixes and use plain dotted numbers: '90', '90.1', or '90.1.2'
- Limit to at most three numeric components (drop the fourth)
- Parse User-Agent or release strings down to major/minor before putting them in the target config
Example fix
// before
let target = { script: { chrome: 'v90.0.2200.3' } }
// after
let target = { script: { chrome: '90.0' } } Defensive patterns
Strategy: validation
Validate before calling
let isValidVersion = (value: string) => /^\d+(\.\d+){0,2}$/.test(value)
for (let [key, value] of Object.entries(browserTargets)) {
if (!isValidVersion(value)) throw new Error(`${key} version must be X, X.Y, or X.Y.Z`)
} Type guard
let isDottedVersion = (value: unknown): value is string =>
typeof value === 'string' && /^\d+(\.\d+){0,2}$/.test(value) Prevention
- Normalize User-Agent-derived versions to at most 3 numeric components
- Never pass 'latest', ranges, or prefixed versions into target config
When it happens
Trigger: `{ script: { chrome: 'v90' } }`, `{ style: { safari: 'latest' } }`, `{ script: { edge: '90.0.1.1' } }`, or any version with 4+ components or non-numeric characters.
Common situations: Copying version strings from User-Agent headers or release notes; using browserslist ranges like '>= 90'; prefixing versions with 'v' as is common in release tags.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- basePath must be a string
- fingerprint.buildId must be a string
- fingerprint.buildId must be a non-empty string
- files.extensions must be an array
- files.extensions values must be strings
AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27).
Data as JSON: /api/errors/9f1cfb89455ec04f.
Report an issue: GitHub.