apify/crawlee · error · NavigationSkippedError

The `$` property is not available - `skipNavigation` was use

Error message

The `$` property is not available - `skipNavigation` was used

What it means

Same mechanism as `body`: with `skipNavigation` used, the response is not parsed, so the `$` (CheerioAPI) property is a getter that throws NavigationSkippedError. Crawlee throws instead of returning a broken/undefined parser so the mistake fails loudly.

Source

Thrown at packages/cheerio-crawler/src/internals/cheerio-crawler.ts:254

            const $ = cheerio.load(dom, {
                xml: { decodeEntities: true, xmlMode: isXml },
            } as CheerioOptions);

            return {
                $,
                body,
            };
        } catch (err) {
            if (err instanceof NavigationSkippedError) {
                return {
                    get body(): string {
                        throw new NavigationSkippedError(
                            'The `body` property is not available - `skipNavigation` was used',
                            { cause: err },
                        );
                    },
                    get $(): CheerioAPI {
                        throw new NavigationSkippedError(
                            'The `$` property is not available - `skipNavigation` was used',
                            { cause: err },
                        );
                    },
                };
            }

            throw err;
        }
    }

    private async addHelpers(crawlingContext: InternalHttpCrawlingContext & { $: CheerioAPI }) {
        const addRequests = crawlingContext.addRequests;

        const extractLinks = async (options?: ExtractLinksOptions): Promise<string[]> => {
            if (!crawlingContext.$) {
                throw new Error('Cannot extract links because the DOM is not available.');
            }

View on GitHub (pinned to dbe57fb09c)

Solutions

  1. Remove all `$(...)` usage from handlers running with skipNavigation.
  2. Disable `skipNavigation` if DOM parsing is required.
  3. Branch inside the handler on the crawler configuration instead of touching `$` unconditionally.

Example fix

// before
new CheerioCrawler({ skipNavigation: true, requestHandler: async ({ $ }) => { await $('h1').text(); } });
// after
new CheerioCrawler({ skipNavigation: true, requestHandler: async ({ request }) => { log.info(request.url); } });
Defensive patterns

Strategy: validation

Validate before calling

if (crawlerOptions.skipNavigation && handlerSource.includes('$(')) { throw new Error('handler uses $ but skipNavigation is enabled'); }

Type guard

function hasCheerio(ctx: CrawlingContext): ctx is CrawlingContext & { $: CheerioAPI } { return (ctx as any).$ !== undefined; }

Try / catch

try { const text = context.$('h1').text(); } catch (err) { if (err instanceof NavigationSkippedError) { log.info('no DOM, skipping parse'); } else { throw err; } }

Prevention

When it happens

Trigger: Accessing `crawlingContext.$` (or destructuring it) in a requestHandler while `skipNavigation: true` is set on the CheerioCrawler or the specific request.

Common situations: Using `$('a').each(...)` or `enqueueLinks` style logic in a handler after enabling skipNavigation; shared request handler reused by both a parsing crawler and a navigation-only crawler.

Related errors


AI-assisted analysis of apify/crawlee@dbe57fb09c (2026-08-30). Data as JSON: /api/errors/c39c5e5aee160579. Report an issue: GitHub.