{"record":{"id":"43ee2cabd4db087f","repo":"apify/crawlee","slug":"cannot-extract-links-because-the-jsdom-is-not-avai","errorCode":null,"errorMessage":"Cannot extract links because the JSDOM is not available.","messagePattern":"Cannot extract links because the JSDOM is not available\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/jsdom-crawler/src/internals/jsdom-crawler.ts","lineNumber":394,"sourceCode":"                    get document(): Document {\n                        throw new NavigationSkippedError(\n                            'The `document` property is not available - `skipNavigation` was used',\n                            { cause: err },\n                        );\n                    },\n                };\n            }\n\n            throw err;\n        }\n    }\n\n    private async addHelpers(crawlingContext: InternalHttpCrawlingContext & { body: string; window: DOMWindow }) {\n        const addRequests = crawlingContext.addRequests;\n\n        const extractLinks = async (options?: ExtractLinksOptions): Promise<string[]> => {\n            if (!crawlingContext.window) {\n                throw new Error('Cannot extract links because the JSDOM is not available.');\n            }\n\n            return extractUrlsFromWindow(\n                crawlingContext.window,\n                options?.selector ?? 'a',\n                options?.baseUrl ?? crawlingContext.request.loadedUrl ?? crawlingContext.request.url,\n            );\n        };\n\n        return {\n            extractLinks,\n            enqueueLinks: async (options: EnqueueLinksOptions = {}) => {\n                const baseUrl = resolveBaseUrlForEnqueueLinksFiltering({\n                    enqueueStrategy: options.strategy,\n                    finalRequestUrl: crawlingContext.request.loadedUrl,\n                    originalRequestUrl: crawlingContext.request.url,\n                    userProvidedBaseUrl: options.baseUrl,\n                });","sourceCodeStart":376,"sourceCodeEnd":412,"githubUrl":"https://github.com/apify/crawlee/blob/dbe57fb09ca607ad59dcf998f3925ef9ac3bb26c/packages/jsdom-crawler/src/internals/jsdom-crawler.ts#L376-L412","documentation":"extractLinks() (and therefore enqueueLinks()) relies on the JSDOM `window` to run querySelectorAll over the parsed document. When the crawling context has no window — typically because navigation was skipped — link extraction is impossible and the error is thrown explicitly instead of returning an empty list.","triggerScenarios":"Calling `context.enqueueLinks()` or `context.extractLinks()` in a JSDOMCrawler request that ran with `skipNavigation: true`, or any path where `crawlingContext.window` is falsy.","commonSituations":"enable enqueuing of links in a fast skipNavigation crawl; handlers reused across normal and skipNavigation requests.","solutions":["Enable navigation (remove `skipNavigation: true`) for requests where you need enqueueLinks/extractLinks.","Extract links from the raw HTML yourself: cheerio.load(context.body) and collect `a[href]`.","Skip the enqueueLinks call when window is unavailable and log a warning instead.","Use CheerioCrawler + enqueueLinks for link discovery on skipNavigation-style crawls."],"exampleFix":"// before (fails with skipNavigation)\nawait context.enqueueLinks();\n\n// after\nif (context.window) {\n    await context.enqueueLinks();\n} else {\n    const $ = (await import('cheerio')).load(context.body);\n    const urls = $('a[href]').map((_, el) => $(el).attr('href')).get();\n    await context.addRequests(urls.map((u) => ({ url: new URL(u, context.request.loadedUrl).href })));\n}","handlingStrategy":"fallback","validationCode":"if (!context.window) {\n    // cannot call enqueueLinks/extractLinks; use cheerio fallback on context.body\n}","typeGuard":"function canExtractLinks(ctx: { window?: unknown }): ctx is { window: DOMWindow } {\n    return Boolean((ctx as any).window);\n}","tryCatchPattern":"try {\n    await context.enqueueLinks();\n} catch (err) {\n    if ((err as Error).message.includes('JSDOM is not available')) {\n        const $ = (await import('cheerio')).load(context.body);\n        const urls = $('a[href]').map((_, el) => $(el).attr('href')!).get()\n            .map((u) => new URL(u, context.request.loadedUrl).href);\n        await context.addRequests(urls.map((url) => ({ url })));\n    } else {\n        throw err;\n    }\n}","preventionTips":["Only call enqueueLinks on requests that produced a JSDOM window.","Implement a cheerio-based link extractor as the standard fallback path.","Keep skipNavigation crawls and link-discovery crawls as separate pipeline stages.","Check `context.window` before any helper that needs the DOM."],"tags":["jsdom","skip-navigation","enqueue-links","dom"],"backgroundTag":"dom-unavailable-skip-navigation","analyzedSha":"dbe57fb09ca607ad59dcf998f3925ef9ac3bb26c","analyzedAt":"2026-08-30T22:22:28.328Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}