cheeriojs/cheerio · error · RangeError

The content-type "${mimeType.essence}" is neither HTML nor X

Error message

The content-type "${mimeType.essence}" is neither HTML nor XML.

What it means

fromURL() fetches a URL and only parses HTML or XML responses. It parses the Content-Type header; if the MIME type is neither HTML nor XML (e.g. application/json, text/plain, image/*), it throws a RangeError because the parser cannot meaningfully process the body.

Source

Thrown at src/index.ts:263

  const promise = new Promise<CheerioAPI>((resolve, reject) => {
    undiciStream = new Client(urlObject.origin)
      .compose(interceptors.redirect({ maxRedirections: 5 }))
      .stream(streamOptions, (res) => {
        if (res.statusCode < 200 || res.statusCode >= 300) {
          throw new errors.ResponseError('Response Error', res.statusCode, {
            headers: res.headers,
          });
        }

        const contentTypeHeader = res.headers['content-type'] ?? 'text/html';
        const mimeType = new MIMEType(
          Array.isArray(contentTypeHeader)
            ? contentTypeHeader[0]
            : contentTypeHeader,
        );

        if (!(mimeType.isHTML() || mimeType.isXML())) {
          throw new RangeError(
            `The content-type "${mimeType.essence}" is neither HTML nor XML.`,
          );
        }

        // Forward the charset from the header to the decodeStream.
        encoding.transportLayerEncodingLabel =
          mimeType.parameters.get('charset');

        /*
         * If we allow redirects, we will have entries in the history.
         * The last entry will be the final URL.
         */
        const history = (
          res.context as
            | {
                history?: URL[];
              }
            | undefined

View on GitHub (pinned to a1be131f9b)

Solutions

  1. Verify the URL actually returns HTML (curl -I <url>) and fix the URL/endpoint
  2. If the server mislabels HTML, fetch manually and pass the body to cheerio.load(body) with xml/HTML parsing forced
  3. Handle JSON endpoints with a JSON parser instead of cheerio
  4. Catch the RangeError and fall back to manual fetch + load

Example fix

// before
const $ = await cheerio.fromURL('https://api.example.com/v1/page');
// after
const res = await fetch('https://api.example.com/v1/page');
const $ = cheerio.load(await res.text()); // parse regardless of content-type
Defensive patterns

Strategy: fallback

Validate before calling

const res = await fetch(url);
const type = res.headers.get('content-type') ?? '';
if (!/html|xml/i.test(type)) {
  throw new Error(`Refusing to parse ${type} from ${url}`);
}
const $ = await cheerio.fromURL(url);

Type guard

const isHtmlOrXml = (contentType: string | null): boolean =>
  /text\/html|application\/x?html|\+xml|application\/xml/i.test(contentType ?? '');

Try / catch

try {
  const $ = await cheerio.fromURL(url);
} catch (e) {
  if (e instanceof RangeError && /neither HTML nor XML/.test(e.message)) {
    const res = await fetch(url);
    return cheerio.load(await res.text()); // manual fallback
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling cheerio.fromURL() on an endpoint that returns JSON (an API URL instead of a page), a text file, a redirect landing on a download, or a misconfigured server sending the wrong Content-Type.

Common situations: Pointing fromURL at a REST API instead of the HTML page; servers responding with application/octet-stream or text/plain for HTML; SPA backends returning JSON for unknown routes; API gateways rewriting content types.

Related errors


AI-assisted analysis of cheeriojs/cheerio@a1be131f9b (2026-08-28). Data as JSON: /api/errors/daef83001a4ff0d2. Report an issue: GitHub.