apify/crawlee · error · NavigationSkippedError
The `document` property is not available - `skipNavigation`
Error message
The `document` property is not available - `skipNavigation` was used
What it means
Same skipNavigation mechanism: `context.document` normally returns the JSDOM `window.document`, but with navigation skipped no document exists, so the getter throws NavigationSkippedError to prevent silent `undefined` DOM operations.
Source
Thrown at packages/jsdom-crawler/src/internals/jsdom-crawler.ts:377
},
};
} catch (err) {
if (err instanceof NavigationSkippedError) {
return {
get window(): DOMWindow {
throw new NavigationSkippedError(
'The `window` property is not available - `skipNavigation` was used',
{ cause: err },
);
},
get body(): string {
throw new NavigationSkippedError(
'The `body` property is not available - `skipNavigation` was used',
{ cause: err },
);
},
get document(): Document {
throw new NavigationSkippedError(
'The `document` property is not available - `skipNavigation` was used',
{ cause: err },
);
},
};
}
throw err;
}
}
private async addHelpers(crawlingContext: InternalHttpCrawlingContext & { body: string; window: DOMWindow }) {
const addRequests = crawlingContext.addRequests;
const extractLinks = async (options?: ExtractLinksOptions): Promise<string[]> => {
if (!crawlingContext.window) {
throw new Error('Cannot extract links because the JSDOM is not available.');
}View on GitHub (pinned to dbe57fb09c)
Solutions
- Remove `skipNavigation: true` from the request/crawler options.
- Parse `context.body` with cheerio instead of using document.querySelector.
- Route DOM-requiring requests to a non-skipNavigation crawler variant.
- Check `request` options at the top of the handler and bail out before DOM access.
Example fix
// before (skipNavigation: true)
const title = context.document.querySelector('title')?.textContent;
// after
const cheerio = await import('cheerio');
const $ = cheerio.load(context.body);
const title = $('title').text(); Defensive patterns
Strategy: try-catch
Validate before calling
if (request.skipNavigation === true) {
throw new Error('This handler requires a document; disable skipNavigation for this request');
} Type guard
function hasDocument(ctx: object): ctx is { document: Document } {
return 'document' in ctx && (ctx as any).document !== undefined;
} Try / catch
let $: cheerio.CheerioAPI | null = null;
try {
const doc = context.document;
$ = loadFromDocument(doc);
} catch (err) {
if ((err as Error).message.includes('`skipNavigation` was used')) {
$ = (await import('cheerio')).load(context.body);
} else {
throw err;
}
} Prevention
- Route document-dependent handlers to crawlers without skipNavigation.
- Centralize DOM access behind a helper that falls back to cheerio parsing.
- Assert at crawl start which requests are skipNavigation and which handlers they use.
- Log request.userData.skipNavigation in error reports for faster diagnosis.
When it happens
Trigger: Accessing `context.document` (e.g. `context.document.querySelector(...)`) inside a JSDOMCrawler handler running with `skipNavigation: true`.
Common situations: Parsing pages with document APIs after enabling skipNavigation for performance; running preNavigationHooks/postNavigationHooks that use document; shared crawlers where some request options set skipNavigation dynamically.
Related errors
- The `window` property is not available - `skipNavigation` wa
- The `body` property is not available - `skipNavigation` was
- Cannot extract links because the JSDOM is not available.
- The `window` property is not available - `skipNavigation` wa
- The `body` property is not available - `skipNavigation` was
AI-assisted analysis of apify/crawlee@dbe57fb09c (2026-08-30).
Data as JSON: /api/errors/887ee4dff8d271b5.
Report an issue: GitHub.