remix-run/remix · error · ParseError

missing hostname

Error message

missing hostname

What it means

parsePattern splits a pattern source into protocol/hostname/port/pathname spans. If the pattern declares a port but no hostname (e.g. 'https://:8080/x'), there is nothing to attach the port to, which is structurally invalid per the URL spec. A ParseError with code 'missing hostname' is thrown pointing at the character just before the port span.

Source

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

  PartPattern,
  PartPatternToken,
  RoutePattern,
} from '../route-pattern.ts'
import type { Mutable } from '../types/utils.ts'

const IDENTIFIER_RE = /^[a-zA-Z_$][a-zA-Z_$0-9]*/

/**
 * Parse a route pattern source string
 *
 * @param source The pattern source, e.g. `'/users/:id'` or `'https://:tenant.example.com/dashboard?tab'`.
 * @returns The parsed pattern.
 * @throws {ParseError} When the source is malformed.
 */
export function parsePattern<source extends string>(source: source): RoutePattern<source> {
  let spans = split(source)
  if (spans.port && !spans.hostname) {
    throw new ParseError('missing hostname', source, spans.port[0] - 1)
  }

  return createRoutePattern<source>({
    protocol: parseProtocol(source, spans.protocol),
    hostname: parseHostname(source, spans.hostname),
    port: spans.port ? source.slice(...spans.port) : null,
    pathname: spans.pathname
      ? parsePart(source, { span: spans.pathname, type: 'pathname' })
      : parsePart('', { span: [0, 0], type: 'pathname' }),
    search: spans.search ? parseSearch(source.slice(...spans.search)) : new Map(),
  })
}

/**
 * Parse a single URL part (hostname or pathname).
 *
 * @private
 */

View on GitHub (pinned to 9696913134)

Solutions

  1. Add a hostname before the port: 'https://example.com:8080/x'
  2. Use a wildcard host if any host is acceptable: 'https://*:8080/x'
  3. Drop the port if it's not needed
  4. Validate pattern sources with parseRoutePattern in a unit test before registering them

Example fix

// before
let pattern = parseRoutePattern('https://:8080/users/:id')

// after
let pattern = parseRoutePattern('https://example.com:8080/users/:id')
Defensive patterns

Strategy: validation

Validate before calling

try { parseRoutePattern(source) } catch (e) { /* reject at config load time, before app boots */ }

Type guard

function isValidPatternSource(source: string): boolean { try { parseRoutePattern(source); return true } catch { return false } }

Try / catch

try { parseRoutePattern(source) } catch (e) { if (e instanceof ParseError && e.message === 'missing hostname') { /* add host or reject */ } }

Prevention

When it happens

Trigger: Calling parseRoutePattern (or APIs that parse, like `pattern`/`partsOf`/matcher insert) with a source containing `:port` but no host, such as ':8080/x' or 'https://:8080/x'.

Common situations: Building pattern strings by template concatenation and omitting the host variable; converting relative URL patterns to absolute ones incompletely; config-driven patterns where the host placeholder was accidentally empty.

Related errors


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