Skip to content

RelativeURL

When to use

Use RelativeURL when you need URL parsing and manipulation but want relative paths instead of absolute URLs. Common in single-page applications where links should produce /path?query#hash rather than http://localhost:3000/path?query#hash. Also useful for server-side route generation where the origin is irrelevant.

The pattern

A RelativeURL class extends the built-in URL. The constructor provides a dummy base so that relative paths can be parsed without requiring an origin. Getters for href, origin, protocol, username, password, host, hostname, and port are overridden to return empty strings or the relative form.

Basic usage

Construct a RelativeURL with a path string. It parses like a standard URL but strips the origin from output — href returns only the path, query, and hash:

javascript
const url = new RelativeURL("/users?page=2#top");

url.href;       // "/users?page=2#top"
url.pathname;   // "/users"
url.search;     // "?page=2"
url.hash;       // "#top"
url.origin;     // ""
url.hostname;   // ""

Parsing a relative path

Because URL requires a base, RelativeURL supplies a dummy origin so relative paths parse correctly:

javascript
const url = new RelativeURL("/api/items/42");
url.pathname;   // "/api/items/42"
url.href;       // "/api/items/42"

Modifying URL components

All inherited URL setters (like searchParams) still work. Only the output is relative:

javascript
const url = new RelativeURL("/search");
url.searchParams.set("q", "hello world");
url.searchParams.set("page", "1");

url.href;
// "/search?q=hello+world&page=1"

Updating href

Assigning to href re-parses through RelativeURL so the result stays relative:

javascript
const url = new RelativeURL("/old");
url.href = "/new?updated=true";

url.pathname; // "/new"
url.search;   // "?updated=true"
url.href;     // "/new?updated=true"

SPA navigation

Build relative links for history.pushState() without an origin prefix:

javascript
function navigate(path) {
  const url = new RelativeURL(path);
  history.pushState(null, "", url.href);
}

navigate("/dashboard?tab=settings#profile");
// pushes "/dashboard?tab=settings#profile"

Implementing RelativeURL

The class extends URL with a dummy base so that relative paths parse without an origin. Getters for href, origin, protocol, and other origin-related properties are overridden to return empty strings or the relative form.

javascript
export class RelativeURL extends URL {
  constructor(input, base = "http://unspecified") {
    super(input, base);
  }
  
  get href() {
    return this.pathname + this.search + this.hash;
  }
  
  set href(value) {
    const url = new RelativeURL(value);
    this.pathname = url.pathname;
    this.search = url.search;
    this.hash = url.hash;
  }
  
  get origin() { return ""; }
  get protocol() { return ""; }
  get username() { return ""; }
  get password() { return ""; }
  get host() { return ""; }
  get hostname() { return ""; }
  get port() { return ""; }
}

Trade-offs

  • Pros: Full URL API (searchParams, pathname manipulation) without origin leaking into output. Drop-in replacement anywhere a relative href is needed. instanceof URL returns true.
  • Cons: The dummy base "http://unspecified" means toString() (inherited from URL) still returns an absolute URL unless you use .href. Callers must use .href for relative output.
  • Versus string concatenation: RelativeURL gives you proper encoding, searchParams, and parsing. Concatenating path segments manually is error-prone with special characters and query strings.
  • Versus URL with real base: Use new URL(path, base) when you actually need absolute URLs. Use RelativeURL when the origin is irrelevant or should be stripped.