apify/crawlee · error · TimeoutError
Navigation timed out after ${this.#navigationTimeoutMillis /
Error message
Navigation timed out after ${this.#navigationTimeoutMillis / 1000} seconds. What it means
Browser crawlers wrap navigation steps in a windowGuard middleware that enforces the crawler's navigationTimeoutMillis budget. If the remaining navigation window is exhausted (or already <= 0) before a navigation step runs, a TimeoutError is thrown stating the total navigation time budget that elapsed.
Source
Thrown at packages/browser-crawler/src/internals/browser-crawler.ts:476
action: (ctx: Ctx) => Awaitable<void | Partial<Ctx>>,
): ContextMiddleware<Ctx, Partial<Ctx>> => ({
action: async (ctx) => (ctx.request.skipNavigation ? {} : ((await action(ctx)) ?? {})),
});
super({
...basicCrawlerOptions,
contextPipelineBuilder: () => {
// A single navigation window covers the pre-navigation hooks, the navigation, and the
// post-navigation hooks: the whole phase shares one `navigationTimeoutSecs` budget, so a slow
// hook eats into the same window the navigation uses. The navigation itself is bounded by
// capping its `gotoOptions.timeout` to the remaining budget.
const windowGuard = <Ctx extends Context>(
step: (ctx: Ctx) => Awaitable<void | Partial<Ctx>>,
): ContextMiddleware<Ctx, Partial<Ctx>> =>
skipGuard(async (ctx: Ctx) => {
const remaining = remainingNavigationWindowMillis(ctx, this.#navigationTimeoutMillis);
if (remaining <= 0) {
throw new TimeoutError(
`Navigation timed out after ${this.#navigationTimeoutMillis / 1000} seconds.`,
);
}
return addTimeoutToPromise(
async () => step(ctx),
remaining,
`Navigation timed out after ${this.#navigationTimeoutMillis / 1000} seconds.`,
);
});
let pipeline = contextPipelineBuilder().compose({ action: this.prepareNavigation.bind(this) });
for (const hook of this.#preNavigationHooks) {
pipeline = pipeline.compose(windowGuard(hook));
}
pipeline = pipeline.compose(skipGuard(this.navigate.bind(this)));
View on GitHub (pinned to dbe57fb09c)
Solutions
- Increase navigationTimeoutMillis in the crawler options
- Speed up page loads (block unnecessary resources, disable images) so navigation fits in the budget
- Handle the TimeoutError in the request handler / rely on crawler retry behavior for transient slowness
Example fix
// before
const crawler = new PuppeteerCrawler({ navigationTimeoutMillis: 15000 });
// after
const crawler = new PuppeteerCrawler({ navigationTimeoutMillis: 60000 }); Defensive patterns
Strategy: retry
Try / catch
try { /* navigation happens inside crawler.run */ } catch (e) { if (e instanceof TimeoutError && /Navigation timed out/.test(e.message)) { /* retry request or raise navigationTimeoutMillis */ } else throw e; } Prevention
- Size navigationTimeoutMillis above your slowest target page's load time
- Block heavy resources to keep navigation fast
- Rely on crawler maxRequestRetries for transient slowness
When it happens
Trigger: The cumulative time spent on navigation (goto plus related steps) exceeds navigationTimeoutMillis; a previous navigation consumed most of the budget and a subsequent step starts with no time left; navigationTimeoutMillis set too low for a slow site.
Common situations: Slow sites or heavy pages exceeding the default 60s navigation timeout; flaky networks; users lowering navigationTimeoutMillis aggressively; multiple enqueuing/navigation steps sharing one budget.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- The `response` property is not available. This might mean th
- The `gotoOptions` property is not available until `prepareNa
- Navigation timed out after ${this.#navigationTimeoutMillis /
- The current SessionPool instance couldn't find a valid sessi
- The `request.loadedUrl` property is not available - `skipNav
AI-assisted analysis of apify/crawlee@dbe57fb09c (2026-08-30).
Data as JSON: /api/errors/7f4d3fdb4bb8e6e4.
Report an issue: GitHub.