facebook/docusaurus · error · TypeError

Url must be a string. Received ${typeof component}

Error message

Url must be a string. Received ${typeof component}

What it means

Thrown by normalizeUrl() inside the per-segment loop when an element of the input array is not a string. normalizeUrl is a URL-join utility that walks each segment assuming it is a string; a non-string element (number, undefined, null, object) is a type error in the caller. The error reports the typeof of the offending segment.

Source

Thrown at packages/docusaurus-utils/src/urlUtils.ts:57

    if (first.startsWith('file:') && urls[0].startsWith('/')) {
      // Force a double slash here, else we lose the information that the next
      // segment is an absolute path
      urls[0] = `${first}//${urls[0]}`;
    } else {
      urls[0] = first + urls[0];
    }
  }

  // There must be two or three slashes in the file protocol,
  // two slashes in anything else.
  const replacement = urls[0].match(/^file:\/\/\//) ? '$1:///' : '$1://';
  urls[0] = urls[0].replace(/^(?<protocol>[^/:]+):\/*/, replacement);

  for (let i = 0; i < urls.length; i += 1) {
    let component = urls[i];

    if (typeof component !== 'string') {
      throw new TypeError(`Url must be a string. Received ${typeof component}`);
    }

    if (component === '') {
      if (i === urls.length - 1 && hasEndingSlash) {
        resultArray.push('/');
      }
      continue;
    }

    if (component !== '/') {
      if (i > 0) {
        // Removing the starting slashes for each component but the first.
        component = component.replace(
          /^\/+/,
          // Special case where the first element of rawUrls is empty
          // ["", "/hello"] => /hello
          component.startsWith('/') && !hasStartingSlash ? '/' : '',
        );

View on GitHub (pinned to 3f483e80e3)

Solutions

  1. Inspect the error's typeof hint and trace which segment of the input array matches that type.
  2. Coerce or default every segment to a string before calling normalizeUrl (e.g. `segment ?? ''` or String(segment)).
  3. Filter out non-string segments upstream with arr.filter((s): s is string => typeof s === 'string') if empty/missing segments should be dropped.
  4. Add a runtime assertion at the boundary where the array is assembled so the bug surfaces at its source.

Example fix

// before
normalizeUrl([baseUrl, maybeUndefinedVersion, slug]);

// after
normalizeUrl([baseUrl, version ?? '', slug].filter(Boolean));
Defensive patterns

Strategy: type-guard

Validate before calling

function allStrings(segments: unknown[]): segments is string[] {
  return segments.every(s => typeof s === 'string');
}

if (!allStrings(rawUrls)) {
  rawUrls = rawUrls.filter((s): s is string => typeof s === 'string');
}
normalizeUrl(rawUrls);

Type guard

function isStringArray(arr: unknown): arr is string[] {
  return Array.isArray(arr) && arr.every(s => typeof s === 'string');
}

Try / catch

try {
  normalizeUrl(rawUrls);
} catch (err) {
  if (err instanceof TypeError && err.message.startsWith('Url must be a string')) {
    // a non-string segment slipped in; filter and retry
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling normalizeUrl([...segments]) where at least one segment is not a string — e.g. a variable that evaluated to undefined, a number accidentally included, or an object produced by a buggy expression. The check is reached for every segment after the protocol-normalization step, so even segments beyond the first are type-checked.

Common situations: Building a URL from config values where one optional field was undefined. Spread of an array that contains nulls. A locale or version variable that is a number rather than a string. Calling normalizeUrl on a value that came from JSON where the field was absent.

Related errors


AI-assisted analysis of facebook/docusaurus@3f483e80e3 (2026-08-12). Data as JSON: /api/errors/4c74be91ff5efc3b. Report an issue: GitHub.