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

  1. Strip prefixes and use plain dotted numbers: '90', '90.1', or '90.1.2'
  2. Limit to at most three numeric components (drop the fourth)
  3. 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

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


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