angular/angular · error · Error

Bad URL - Cannot parse URL: ${url}

Error message

Bad URL - Cannot parse URL: ${url}

What it means

After the double-slash check passes, parseAppUrl() delegates to UrlCodec.parse(url, serverBase); the codec signals failure by returning a string instead of a parsed object, and the shim converts that into 'Bad URL - Cannot parse URL'. It means the URL survived the first check but is still unparseable relative to the server base.

Source

Thrown at packages/common/upgrade/src/location_shim.ts:365

  private getServerBase() {
    const {protocol, hostname, port} = this.platformLocation;
    const baseHref = this.locationStrategy.getBaseHref();
    let url = `${protocol}//${hostname}${port ? ':' + port : ''}${baseHref || '/'}`;
    return url.endsWith('/') ? url : url + '/';
  }

  private parseAppUrl(url: string) {
    if (DOUBLE_SLASH_REGEX.test(url)) {
      throw new Error(`Bad Path - URL cannot start with double slashes: ${url}`);
    }

    let prefixed = url.charAt(0) !== '/';
    if (prefixed) {
      url = '/' + url;
    }
    let match = this.urlCodec.parse(url, this.getServerBase());
    if (typeof match === 'string') {
      throw new Error(`Bad URL - Cannot parse URL: ${url}`);
    }
    let path =
      prefixed && match.pathname.charAt(0) === '/' ? match.pathname.substring(1) : match.pathname;
    this.$$path = this.urlCodec.decodePath(path);
    this.$$search = this.urlCodec.decodeSearch(match.search);
    this.$$hash = this.urlCodec.decodeHash(match.hash);

    // make sure path starts with '/';
    if (this.$$path && this.$$path.charAt(0) !== '/') {
      this.$$path = '/' + this.$$path;
    }
  }

  /**
   * Registers listeners for URL changes. This API is used to catch updates performed by the
   * AngularJS framework. These changes are a subset of the `$locationChangeStart` and
   * `$locationChangeSuccess` events which fire when AngularJS updates its internally-referenced
   * version of the browser URL.

View on GitHub (pinned to 51cb07e980)

Solutions

  1. Pass simple app URLs of the form '/path?query#hash' to $location
  2. Normalize/encode before setting: encodeURI on dynamic segments
  3. Verify the <base href> and LocationUpgradeModule serverBase match the URLs being parsed

Example fix

// before
$location.url(externalUrl); // raw cross-origin/oddly-encoded URL

// after
const appUrl = new URL(externalUrl);
$location.url(appUrl.pathname + appUrl.search + appUrl.hash);
Defensive patterns

Strategy: validation

Validate before calling

function parseableUrl(url: string, base: string): boolean {
  try { new URL(url, base); return true; } catch { return false; }
}
if (!parseableUrl(candidate, serverBase)) throw new Error('Refusing unparseable URL');
$location.url(candidate);

Type guard

function isWellFormedUrlCandidate(u: string): boolean {
  try { new URL(u, 'http://localhost/'); return true; } catch { return false; }
}

Prevention

When it happens

Trigger: $location.url('http://:8080/x') (malformed host:port), URLs with invalid percent-encodings that later decoding rejects, or unusual base tags making the codec return an error string in a hybrid upgrade app.

Common situations: Raw window.location or backend-supplied URLs pushed into $location during upgrade; <base href> mismatches; hand-assembled URL strings with bad encoding.

Related errors


AI-assisted analysis of angular/angular@51cb07e980 (2026-08-22). Data as JSON: /api/errors/a2ad208c0fd8b709. Report an issue: GitHub.