How to Navigate to a URL with Puppeteer
Navigate with Puppeteer’s page.goto(), choose the right completion condition, handle redirects and timeouts, and capture a screenshot when the page is ready.
Use await page.goto('https://example.com') to navigate a Puppeteer page to a URL. Include the scheme (https:// or http://), then choose a waitUntil condition that matches what you need to do next. The default condition is load, and the default navigation timeout is 30 seconds. Puppeteer documents the goto API.
1. Install Puppeteer and navigate
The puppeteer package downloads a compatible Chrome for Testing browser. If your environment already provides a browser, puppeteer-core is an alternative, but you must configure its executable path. The example below uses the standard package.
npm install puppeteer
Save this as navigate.mjs and run it with node navigate.mjs:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
const response = await page.goto('https://example.com');
console.log('Final URL:', page.url());
console.log('HTTP status:', response?.status() ?? 'No main-resource response');
console.log('Page title:', await page.title());
} finally {
await browser.close();
}
goto() resolves to the main resource response. It can resolve to null for cases such as navigating to about:blank or changing only the URL hash, so guard the response before reading its status. A 404 or 500 response is still an HTTP response; check its status explicitly if your workflow treats those statuses as errors. See the API reference.
2. Choose when navigation is complete
waitUntil controls which browser lifecycle event or events Puppeteer waits for before resolving. Pick the earliest condition that makes the next operation safe. A page can continue making requests or rendering application content after a lifecycle event.
| Condition | What it waits for | Useful when |
|---|---|---|
commit |
The response has been received and the document started loading. | You need to know the navigation began and will perform your own readiness check. |
domcontentloaded |
The initial HTML document has been parsed. | You need the document structure and will wait separately for application content. |
load |
The page’s load event fired. This is the default. | You need the document and its load-event resources to finish. |
networkidle0 |
There are no network connections for at least 500 ms. | The page settles its network activity and does not keep background requests open. |
networkidle2 |
There are no more than two network connections for at least 500 ms. | The page may keep a small number of background requests open. |
The lifecycle names and behavior are documented in Puppeteer’s lifecycle event reference. For example, to return after the initial HTML is parsed and then wait for the specific content you need:
await page.goto('https://example.com', {
waitUntil: 'domcontentloaded',
timeout: 45_000,
});
await page.locator('main h1').wait();
For multiple lifecycle conditions, pass an array. Puppeteer waits for all listed events; conditions that are too strict can make navigation slower or time out.
await page.goto('https://example.com', {
waitUntil: ['domcontentloaded', 'networkidle2'],
timeout: 45_000,
});
3. Set navigation timeouts
The per-call timeout option sets a limit for one navigation. The documented default is 30,000 milliseconds. Set timeout: 0 to disable the timeout, but doing so can leave a job waiting indefinitely if the site never reaches the requested condition.
await page.goto('https://example.com', {
waitUntil: 'load',
timeout: 60_000,
});
For a page-wide default that applies to goto, waitForNavigation, reload, and related navigation waits, use setDefaultNavigationTimeout():
page.setDefaultNavigationTimeout(60_000);
await page.goto('https://example.com');
See Puppeteer’s navigation timeout API for the methods affected. Prefer a reasonable finite timeout in unattended scripts, and handle timeout errors so one slow target does not stop unrelated work.
4. Wait for a navigation triggered by a click
When a click causes a full navigation, register the navigation wait before clicking. Starting both promises together avoids a race where the click navigates before Puppeteer begins waiting:
const [response] = await Promise.all([
page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
page.locator('a.next').click(),
]);
console.log('HTTP status:', response?.status() ?? 'Same-document navigation');
waitForNavigation() can return null for same-document URL changes, including History API changes and anchor navigation. If the interaction updates the page without a full document load, wait for the resulting element or application state instead of relying on a main-resource response. See waitForNavigation().
5. Wait for the page state your task needs
A completed navigation does not necessarily mean that a single-page application has fetched and rendered the data you care about. After navigation, wait for a meaningful selector or condition. Puppeteer recommends locators for interactions because they wait for element presence and action preconditions such as visibility and stable layout.
await page.goto('https://example.com/dashboard', {
waitUntil: 'domcontentloaded',
});
const heading = page.locator('[data-testid="dashboard-title"]');
await heading.wait();
console.log(await heading.innerText());
Use a selector that represents the result needed by your script. Avoid arbitrary long sleeps when a specific element can signal readiness. For a screenshot or PDF, the right readiness condition depends on the page: Puppeteer’s screenshot examples use networkidle2, but pages with analytics, polling, or streaming requests may never become idle. In that case, wait for the content you need, then capture.
6. Handle redirects, HTTP errors, and navigation failures
After redirects, page.url() gives the current URL and the response from goto() describes the main resource navigation. Inspect both when the destination matters:
const response = await page.goto('https://example.com/old-path', {
waitUntil: 'domcontentloaded',
});
console.log('Destination:', page.url());
if (response && response.status() >= 400) {
throw new Error(`Navigation returned HTTP ${response.status()}`);
}
Navigation can reject for an invalid URL, SSL errors, an unreachable server, a timeout, or failure to load the main resource. Catch errors at the unit of work so you can log the target and continue or retry according to your own policy:
try {
const response = await page.goto('https://example.com', {
waitUntil: 'domcontentloaded',
timeout: 30_000,
});
console.log('Status:', response?.status() ?? 'No response');
} catch (error) {
console.error('Could not navigate:', error.message);
}
A valid HTTP status such as 404 or 500 does not necessarily make goto() throw, including in headless shell. Check response.status() when your code needs to reject unsuccessful HTTP responses. The API reference describes the return value and caveats.
7. Capture a screenshot after navigation
For a basic screenshot, navigate, wait for the state you require, then save the image. This example uses networkidle2 as one possible choice; replace it with an application-specific wait if the site keeps requests open.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 1000 });
await page.goto('https://example.com', {
waitUntil: 'networkidle2',
timeout: 45_000,
});
await page.screenshot({ path: 'page.png', fullPage: true });
} finally {
await browser.close();
}
8. Common problems and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
Navigation timeout ... exceeded |
The chosen lifecycle condition did not occur in time, or the server is slow. | Check whether the task needs load or whether domcontentloaded plus a selector wait is sufficient. Increase the timeout only when the target legitimately needs longer. |
| The call returns but expected content is missing | Navigation completed before client-side rendering or a data request finished. | Wait for a locator or application-specific state after goto(). |
net::ERR_NAME_NOT_RESOLVED or connection refused |
DNS, network access, proxy, firewall, or server availability problem. | Check the URL from the same runtime environment, including its scheme; verify network and proxy settings. |
| Certificate or SSL error | The target’s certificate is invalid or the runtime does not trust its issuer. | Fix the certificate or trust configuration. Do not disable certificate checks as a routine workaround. |
| HTTP 404 or 500 without a thrown error | goto() returned a response; HTTP error status is separate from a navigation failure. |
Read response.status() and decide explicitly which statuses your workflow accepts. |
The click succeeds but waitForNavigation() returns null |
The app changed the hash or used the History API without loading a new document. | Wait for the resulting URL, selector, or state rather than expecting a main-resource response. |
The script hangs on networkidle0 |
The site maintains polling, analytics, streaming, or other long-lived requests. | Use a less strict lifecycle event such as domcontentloaded, then wait for the specific content needed. |
| Browser process remains open after an error | Cleanup did not run after a rejected navigation. | Place browser work in try/finally and close the browser in the finally block. |
9. Performance, reliability, and cost
Launching a browser and loading a page costs more time and memory than issuing a simple HTTP request because Puppeteer runs a real browser and the page may load scripts, fonts, images, and third-party resources. Reuse one browser process across related work and create separate pages as needed, rather than launching a fresh browser for every URL. Close pages and the browser when finished.
Choose the smallest wait condition that still makes the next step reliable, then explicitly wait for the content the task depends on. This avoids waiting for irrelevant background activity while preventing screenshots or extraction from racing the page. Set bounded navigation timeouts and capture errors with the URL and failure reason so transient site failures are diagnosable. Browser automation has no per-navigation fee by itself, but your runtime, compute, proxy, and hosting costs depend on your infrastructure and workload.
10. Or skip the browser setup
If your goal is a screenshot rather than browser automation, ScreenshotNeo returns a screenshot or PDF from one GET request. It is also an MCP server for AI agents. See the ScreenshotNeo API documentation for parameters.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
- Cookie banners are accepted and removed before capture; newsletter popups and chat widgets are removed too.
- Bot checks, blank pages, timeouts, and failed loads are never billed; cache hits are also free.
- An MCP server lets AI agents use screenshot tools.
- 1,000 screenshots a month are free with no card. Paid plans start at $5 for 3,000 screenshots.
Sign up for 1,000 free screenshots a month, with no card required.
11. FAQ
Does page.goto() follow redirects?
It navigates through the browser’s normal request flow. Inspect page.url() after it resolves to see the current destination.
Can I navigate to a URL without a scheme?
Use a complete URL such as https://example.com. Including the scheme removes ambiguity about how the address should be interpreted.
Why can a successful navigation return null?
Some navigations do not load a new main document, such as changing only a hash or navigating to about:blank. In these cases there may be no main-resource response object.
Should I always wait for network idle before a screenshot?
No. Network idle is useful when the page settles, but background requests can prevent it from occurring. Wait for the visual content your capture depends on.


