Skip to content
Puppeteer · error message

Puppeteer: Navigation timeout of 30000 ms exceeded

RetryableNavigation & network

Last verified

The exact message
TimeoutError: Navigation timeout of 30000 ms exceeded

What it means

A navigation started by page.goto(), page.waitForNavigation(), page.reload() or page.goBack() did not reach its waitUntil condition within the navigation timeout, 30 seconds by default. The default condition is load, which waits for every image, script, stylesheet and iframe on the page. networkidle0 and networkidle2 are stricter still, requiring the network to go quiet for 500 ms.

The message does not say which resource held the page open. Very often the HTML arrived quickly and the document is usable; a slow analytics script, a hanging ad request or a long-polling connection simply kept the load event from firing. A second common pattern is page.waitForNavigation() started after a click whose navigation had already completed, so it waits for a navigation that never comes.

What is not wrong: DNS and connectivity. Those fail fast with net::ERR_... errors instead.

What it looks like

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com/', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('h1');

// Click that causes a full navigation
await Promise.all([
  page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
  page.click('a.next'),
]);
await browser.close();

Why it happens

  • waitUntil: 'networkidle0' on a page with analytics, websockets or long-polling, so the network never goes idle.
  • A third-party resource (ads, chat widget, fonts) loads slowly and delays the load event.
  • page.waitForNavigation() is awaited after the click, missing a navigation that already finished.
  • The click triggers a client-side route change (history.pushState), which is not a full navigation in the default sense.
  • A bot-protection challenge, slow proxy or overloaded server delays the response.

How to fix it

  1. Wait for less: page.goto(url, { waitUntil: 'domcontentloaded' }) and then wait for the element you need.
  2. Start the navigation wait before the click: await Promise.all([page.waitForNavigation(), page.click('a.next')]).
  3. For single-page apps, wait for a selector or URL change rather than a navigation event.
  4. Abort irrelevant requests with page.setRequestInterception(true) and request.abort() for images, fonts or trackers.
  5. Raise the limit only when the page is genuinely slow: page.setDefaultNavigationTimeout(60000) or { timeout: 60000 }.

How to stop it recurring

Separate "the document arrived" from "the content I need is ready": navigate with domcontentloaded and then wait for a concrete selector. Avoid networkidle0 on modern sites. Always arm waitForNavigation before the action that triggers it, and block heavy third-party traffic in scraping jobs where it adds nothing.

Official documentation: pptr.dev/api/puppeteer.page.goto. See all Puppeteer error messages.

Related errors

Definitions

Quick answers

What does "Navigation timeout of 30000 ms exceeded" mean in Puppeteer?

A navigation started by page.goto() , page.waitForNavigation() , page.reload() or page.goBack() did not reach its waitUntil condition within the navigation timeout, 30 seconds by default. The default condition is load , which waits for every image, script, stylesheet and iframe on the page. networkidle0 and networkidle2 are stricter still, requiring the network to go quiet for 500 ms.

How do I fix "Navigation timeout of 30000 ms exceeded"?

1. Wait for less: page.goto(url, { waitUntil: 'domcontentloaded' }) and then wait for the element you need. 2. Start the navigation wait before the click: await Promise.all([page.waitForNavigation(), page.click('a.next')]). 3. For single-page apps, wait for a selector or URL change rather than a navigation event. 4. Abort irrelevant requests with page.setRequestInterception(true) and request.abort() for images, fonts or trackers. 5. Raise the limit only when the page is genuinely slow: page.setDefaultNavigationTimeout(60000) or { timeout: 60000 }.

Run browser automation you can watch and take over

Autonoly runs each job in a real cloud Chromium browser driven by Playwright. Timeouts and detached elements still happen; the difference is that you can watch the run live, see the step that failed, and take control of the browser to finish it.

Also see Live browser control.