apify/crawlee · error · Error
An extracted URL: ${href} is relative and options.baseUrl is
Error message
An extracted URL: ${href} is relative and options.baseUrl is not set. Use options.baseUrl in enqueueLinks() to automatically resolve relative URLs. What it means
`extractUrlsFromPage()` validates each href extracted from the page via a `enqueueLinks` selector. If a href is relative (fails the `^[a-z][a-z0-9+.-]*:` absolute-URL test) and no `baseUrl` option was provided, it throws immediately with a clear message instead of letting the later `Request` constructor fail with a confusing invalid-URL error. `baseUrl` is what tells the crawler how to resolve relative links against the page's origin.
Source
Thrown at packages/browser-crawler/src/internals/browser-crawler.ts:907
baseUrl: string,
): Promise<string[]> {
const urls =
(await page.$$eval(selector, (linkEls: HTMLLinkElement[]) =>
linkEls.map((link) => link.getAttribute('href')).filter((href) => !!href),
)) ?? [];
const [base] = await page.$$eval('base', (els: HTMLLinkElement[]) => els.map((el) => el.getAttribute('href')));
const absoluteBaseUrl = base && tryAbsoluteURL(base, baseUrl);
if (absoluteBaseUrl) {
baseUrl = absoluteBaseUrl;
}
return urls
.map((href: string) => {
// Throw a meaningful error when only a relative URL would be extracted instead of waiting for the Request to fail later.
const isHrefAbsolute = /^[a-z][a-z0-9+.-]*:/.test(href); // Grabbed this in 'is-absolute-url' package.
if (!isHrefAbsolute && !baseUrl) {
throw new Error(
`An extracted URL: ${href} is relative and options.baseUrl is not set. ` +
'Use options.baseUrl in enqueueLinks() to automatically resolve relative URLs.',
);
}
return baseUrl ? tryAbsoluteURL(href, baseUrl) : href;
})
.filter((href: string | undefined) => !!href);
}
View on GitHub (pinned to dbe57fb09c)
Solutions
- Pass `baseUrl` to `enqueueLinks()`: `await enqueueLinks({ baseUrl: request.loadedUrl, ... })` — the loaded request URL is the usual base
- Use the convenience `context.enqueueLinks()` inside the requestHandler where `baseUrl` resolution is wired up; on the raw `extractUrlsFromPage` path, always supply the current page URL as third argument
- Preprocess hrefs with `new URL(href, pageUrl).href` if you extract links yourself before enqueueing
Example fix
// before
await enqueueLinks({ selector: 'a.product' }); // relative hrefs on page -> throws
// after
await enqueueLinks({
selector: 'a.product',
baseUrl: request.loadedUrl ?? request.url,
strategy: EnqueueStrategy.SameHostname,
}); Defensive patterns
Strategy: validation
Validate before calling
// Normalize hrefs before enqueueing and assert a base is available:
const base = request.loadedUrl ?? request.url;
const hrefs = [...await page.$evalAll('a[href]', els => els.map(e => e.getAttribute('href')))];
for (const href of hrefs) {
if (!/^[a-z][a-z0-9+.-]*:/i.test(href) && !base) {
throw new Error(`Relative href "${href}" requires a baseUrl`);
}
} Type guard
function isAbsoluteUrl(href: string): boolean {
return /^[a-z][a-z0-9+.-]*:/i.test(href);
} Try / catch
try {
await enqueueLinks({ baseUrl: request.loadedUrl });
} catch (e) {
if (e instanceof Error && /is relative and options.baseUrl is not set/.test(e.message)) {
log.warning(`Skipping link extraction without baseUrl: ${e.message}`);
} else {
throw e;
}
} Prevention
- Always pass baseUrl (request.loadedUrl) when calling enqueueLinks on arbitrary pages
- Prefer context.enqueueLinks() which resolves the base for you
- Normalize links with new URL(href, pageUrl) if you extract them manually
When it happens
Trigger: Calling `enqueueLinks({ selector: 'a' })` (or the crawler's link extraction) without `options.baseUrl` on a page whose anchors use relative hrefs like `/about` or `../page.html`. The page's `<base>` tag is absent or itself relative, so no absolute base can be derived.
Common situations: Scraping sites that use root-relative or relative hrefs in navigation menus (very common); calling `enqueueLinks` manually from `requestHandler` and forgetting `baseUrl`; pages rendered from string templates or file:// contexts where relative paths abound.
Related errors
- The `minConcurrency`/`maxConcurrency`/`initialConcurrency`/`
- The `requestManager` option cannot be used in conjunction wi
- Cannot decide what to purge before running again: `sameDomai
- Failed to infer format from the path: '${path}'. Supported f
- Unsupported format: '${format}'. Use one of ${supportedForma
AI-assisted analysis of apify/crawlee@dbe57fb09c (2026-08-30).
Data as JSON: /api/errors/004dc0d76493449c.
Report an issue: GitHub.