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[];
}
| undefinedView on GitHub (pinned to a1be131f9b)
Solutions
- Verify the URL actually returns HTML (curl -I <url>) and fix the URL/endpoint
- If the server mislabels HTML, fetch manually and pass the body to cheerio.load(body) with xml/HTML parsing forced
- Handle JSON endpoints with a JSON parser instead of cheerio
- 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
- Check Content-Type with curl -I before scraping new URLs
- Expect APIs to return JSON — parse those with JSON.parse, not cheerio
- Wrap scrapers so a bad content-type degrades gracefully instead of crashing
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
- Bad combination of arguments.
- Expected a string
- cheerio.load() expects a string
- Unexpected type of selector
AI-assisted analysis of cheeriojs/cheerio@a1be131f9b (2026-08-28).
Data as JSON: /api/errors/daef83001a4ff0d2.
Report an issue: GitHub.