How to Wait for Page to Load in Puppeteer

Idowu Omisola
Idowu Omisola
April 3, 2025 · 4 min read

When you're web scraping with Puppeteer, you need to make sure that there's proper timing for all page elements to be fully loaded. Without appropriate waiting techniques, your scraper can miss important data and even encounter errors when trying to interact with elements that haven't loaded yet.

So, here are some of the most essential Puppeteer methods for handling page loads effectively across different scenarios.

1. waitForNetworkIdle

The waitForNetworkIdle method ensures all network activity has settled before proceeding with your scraping operation. This method is particularly useful when dealing with pages that continue to make background API calls after the initial load event has fired.

You can use this method to wait until the page hasn't made any new network requests for a specified period.

The following code navigates to a JavaScript-rendered page, waits for all network activity to settle, and then extracts the first product name:

Example
const puppeteer = require("puppeteer");

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  // navigate to the page
  await page.goto("https://www.scrapingcourse.com/javascript-rendering");

  // wait for network to be idle (no more than 2 connections for at least 500ms)
  await page.waitForNetworkIdle({ idleTime: 500, timeout: 10000 });

  // now the page should be fully loaded with dynamic content
  const productName = await page.evaluate(() => {
    // extract the first product name
    return document.querySelector(".product-name").textContent;
  });

  console.log("First product name:", productName);
  await browser.close();
})();
Featured
How to do Web Scraping with Puppeteer and NodeJS
Create a powerful scraper with Puppeteer with this step-by-step tutorial and do headless browser web scraping.

2. waitForSelector

The waitForSelector method provides a targeted approach to waiting for specific elements to appear on the page. Instead of waiting for the entire page to load, this method waits until a particular DOM element matching your CSS selector is visible or available.

It allows your scraper to proceed as soon as the element you need is ready, even if other parts of the page are still loading.

The following code navigates to a JavaScript-rendered products page and waits specifically for the first product element to load before extracting its name:

Example
const puppeteer = require("puppeteer");

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  // navigate to the page
  await page.goto("https://www.scrapingcourse.com/javascript-rendering");

  // wait specifically for the product element to appear in the DOM
  await page.waitForSelector(".product-name", { timeout: 10000 });

  // once the selector is available, extract the data
  const productName = await page.evaluate(() => {
    return document.querySelector(".product-name").textContent.trim();
  });

  console.log("First product name:", productName);
  await browser.close();
})();
Frustrated that your web scrapers are blocked once and again?
ZenRows API handles rotating proxies and headless browsers for you.
Try for FREE

3. Using waitUntil Options

The waitUntil parameter provides various wait conditions for page.goto and page.waitForNavigation methods. This gives you granular control over when Puppeteer should consider a navigation complete.

You can choose from several waitUntil options to match your specific needs. Let's explore how to use each option effectively:

load

The load option waits for the page's load event to fire before considering navigation complete. This event fires when all the initial HTML, CSS, images, and synchronous JavaScript have finished loading. It's suitable for traditional websites where most content loads with the initial page request.

The following code navigates to a JavaScript-rendered page and waits for the load event before extracting the first product name:

Example
const puppeteer = require("puppeteer");

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  // navigate to the page and wait for the load event
  await page.goto("https://www.scrapingcourse.com/javascript-rendering", {
    waitUntil: "load",
  });

  // extract data after the load event has fired
  const productName = await page.evaluate(() => {
    return document.querySelector(".product-name").textContent.trim();
  });

  console.log("First product name:", productName);
  await browser.close();
})();

domcontentloaded

The domcontentloaded option waits for the DOMContentLoaded event to fire before proceeding. This event triggers when the initial HTML document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading. It's faster than the load option since it doesn't wait for all resources.

The following code navigates to the target page and extracts the page title immediately after the DOM is ready, without waiting for images and style sheets to load:

Example
const puppeteer = require("puppeteer");

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  // navigate to the page and wait only for DOM content to load
  await page.goto("https://www.scrapingcourse.com/javascript-rendering", {
    waitUntil: "domcontentloaded",
  });

  // extract the page title as soon as the DOM is ready
  const pageTitle = await page.evaluate(() => {
    return document.querySelector(".page-title").textContent.trim();
  });

  console.log("Page title:", pageTitle);
  await browser.close();
})();

networkidle0

The networkidle0 option is one of the most thorough waiting strategies. It tells Puppeteer to consider navigation complete only when there are zero network connections for at least 500ms. This approach ensures that all asynchronous requests have been completed before proceeding with your scraping task.

The following code navigates to a JavaScript-rendered page and waits until the network is completely idle before extracting product data, ensuring all dynamic content has been loaded:

Example
const puppeteer = require("puppeteer");

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  // navigate to the page and wait for complete network idle
  await page.goto("https://www.scrapingcourse.com/javascript-rendering", {
    waitUntil: "networkidle0",
    timeout: 30000, // increased timeout for complex pages
  });

  // extract data after all network activity has completely stopped
  const productName = await page.evaluate(() => {
    return document.querySelector(".product-name").textContent.trim();
  });

  console.log("First product name:", productName);
  await browser.close();
})();

networkidle2

The networkidle2 option provides a balanced approach to waiting for page loads. It considers navigation complete when there are no more than 2 network connections for at least 500ms. This option is less strict than networkidle0 but still ensures that most essential content has loaded.

This approach works well for most modern websites where some background connections might persist (like analytics or tracking pixels) but don't affect the content you need to scrape. It's often faster than networkidle0 while still being reliable for capturing dynamically loaded content

The following code uses the networkidle2 option to wait until most network activity has settled before extracting product information:

Example
const puppeteer = require("puppeteer");

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  // navigate to the page and wait until no more than 2 network connections
  await page.goto("https://www.scrapingcourse.com/javascript-rendering", {
    waitUntil: "networkidle2",
    timeout: 15000,
  });

  // extract data after most network activity has settled
  const productName = await page.evaluate(() => {
    return document.querySelector(".product-name").textContent.trim();
  });

  console.log("First product name:", productName);
  await browser.close();
})();

Combining waitUntil Options

You can combine two or more waitUntil options to create a more robust waiting strategy for complex scenarios. This is useful when you need to ensure different aspects of page loading are complete, such as when JavaScript and HTML have loaded, but styles and images are still being fetched.

Example
const puppeteer = require("puppeteer");

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  // navigate to the page with multiple wait conditions
  await page.goto("https://www.scrapingcourse.com/javascript-rendering", {
    waitUntil: ["domcontentloaded", "networkidle2"],
    timeout: 20000,
  });

  // extract data after meeting both waiting conditions
  const productName = await page.evaluate(() => {
    return document.querySelector(".product-name").textContent.trim();
  });

  console.log("First product name:", productName);
  await browser.close();
})();

There are more waiting options available in Puppeteer beyond what we've covered here. Each has its own specific use cases depending on your scraping requirements. In the following sections, we'll explore additional techniques for ensuring seamless page loading during your scraping operations.

Seamless Page Loading Without Getting Blocked

Waiting for a page to load isn't always as straightforward as implementing the methods discussed above. Modern websites employ sophisticated anti-bot measures that can delay loading, serve incomplete content, or block your scraper entirely. This is especially common when scraping at scale or targeting sites with valuable data.

ZenRows' Universal Scraper API offers an all-in-one solution to these challenges. It handles the complexities of page loading while bypassing anti-bot measures, significantly reducing the time and effort needed for successful scraping.

It supports JavaScript rendering, premium proxy rotation, headless browser support, CAPTCHA and anti-bot auto-bypass, and everything you need for successful web scraping.

Let's use ZenRows to scrape the Antibot Challenge, a webpage protected by an anti-bot:

Sign up for a free account to get started. You'll be directed to the Request Builder.

building a scraper with zenrows
Click to open the image in full screen

Paste your target URL and enable Premium Proxies and JS Rendering boost mode.

Select Node.js as your programming language and choose the API connection mode. Finally, copy and paste the generated code:

scraper.js
// npm install axios
const axios = require("axios");

const url = "https://www.scrapingcourse.com/antibot-challenge";
const apikey = "<YOUR_ZENROWS_API_KEY>";
axios({
  url: "https://api.zenrows.com/v1/",
  method: "GET",
  params: {
    url: url,
    apikey: apikey,
    js_render: "true",
    premium_proxy: "true",
  },
})
  .then((response) => console.log(response.data))
  .catch((error) => console.log(error));

When you run this code, you'll bypass the anti-bot protection:

Output
<html lang="en">
<head>
    <!-- ... -->
    <title>Antibot Challenge - ScrapingCourse.com</title>
    <!-- ... -->
</head>
<body>
    <!-- ... -->
    <h2>
        You bypassed the Antibot challenge! :D
    </h2>
    <!-- other content omitted for brevity -->
</body>
</html>

Congratulations! You've successfully scraped a protected website with minimal effort.

Conclusion

You explored several effective methods: waitForNetworkIdle for background API calls, waitForSelector for specific elements, and various waitUntil options that each serve different use cases based on website complexity.

These techniques work well for many scenarios, but sophisticated anti-bot measures can still pose challenges. For consistent results without interruptions, consider using a specialized solution like ZenRows that handles JavaScript rendering, proxy rotation, and anti-bot measures automatically. Try ZenRows for free!

Ready to get started?

Up to 1,000 URLs for free are waiting for you