Hardware FixRecommendedDevice not working? Your driver may be the problemCheck updates for common hardware issues.Fix DriversOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan Now×
Skip to content
browser automation

How to Fix Puppeteer page.title() Hanging After Navigation

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Short answer: page.title() only reads the main frame’s title; it does not wait for navigation. If your script appears to hang, identify which promise is still pending, arm navigation waits before clicks, choose a lifecycle condition your page can actually reach, and keep a finite timeout. A reliable click flow is Promise.all([page.waitForNavigation(...), page.click(...)]), followed by await page.title().

What page.title() actually does

Puppeteer’s page.title() method returns a Promise<string>. It is a shortcut for page.mainFrame().title(): it reads the title of the page’s main frame. It is not a navigation waiter and does not replace page.goto(), page.waitForNavigation(), or an application-state check.

That distinction changes the diagnosis. In code such as:

await page.goto(url);
const title = await page.title();

the pending operation may be goto(), not title(). If goto() has resolved and the title call alone remains pending, capture a minimal reproduction and inspect the page and frame state rather than assuming that the title API is waiting for a load event.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Find the promise that is really hanging

Add a log immediately before and after every awaited operation. Record the URL at each boundary so a redirect, single-page-app transition, or unexpected frame is visible.

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

console.log('before goto', page.url());
await page.goto('https://example.com', {waitUntil: 'domcontentloaded'});
console.log('after goto', page.url());

console.log('before title');
const title = await page.title();
console.log('after title', JSON.stringify(title));

await browser.close();
  • If “after goto” never prints, investigate navigation completion, redirects, the selected waitUntil, and the navigation timeout.
  • If “after goto” prints but “after title” does not, save the exact Puppeteer version, browser executable, operating system, URL type, and a minimal script. The documented title operation itself is only a main-frame title read.
  • Use page.url() before and after the action. A changed URL does not necessarily mean that a new document response exists.

Arm a navigation wait before a click

The most common race is starting the click first and waiting afterward. A fast navigation can begin and finish before the waiter is attached. Start both promises together:

const [response] = await Promise.all([
  page.waitForNavigation({waitUntil: 'domcontentloaded'}),
  page.click('a.my-link'),
]);

const title = await page.title();
console.log({url: page.url(), title, response});

Promise.all evaluates its entries before awaiting either one, so the navigation listener is armed before the click can trigger navigation. If the click can fail, the combined promise rejects and you can report the original error instead of continuing with a stale page.

The documented default for waitUntil is 'load'. The example uses 'domcontentloaded' only when DOM availability is sufficient and the page’s load event is delayed by resources that are not needed for the next step.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choose the right navigation completion condition

Navigation completion is a workflow decision, not a universal “wait longer” setting.

Condition Use when Important behavior
load You need the document’s load lifecycle to finish. This is the documented default; analytics, ads, fonts, or other resources can delay it.
domcontentloaded The HTML has been parsed and your next action needs the DOM. It can complete before images and other subresources finish.
An array of events You genuinely require several lifecycle milestones. Every event supplied must fire; one event that never occurs keeps the wait pending until timeout.
Selector or app-state wait A single-page app renders useful content after the URL transition. Wait for the element or state your task needs instead of treating network quiet as proof of readiness.

Network activity can continue after the useful DOM is ready. Conversely, an app may show a loading shell while the network is quiet. Select the condition based on the outcome you need: document completion, DOM availability, a selector, or a separate application-state signal.

await Promise.all([
  page.waitForNavigation({waitUntil: ['domcontentloaded', 'load']}),
  page.click('a.my-link'),
]);

await page.waitForSelector('[data-page-ready]');
const title = await page.title();

Only use an event array when both events are part of your requirement. Otherwise, the stricter condition can create an avoidable timeout.

Understand anchor and History API navigation

Puppeteer treats regular document navigations, anchor changes, and History API URL changes as navigation activity. For an anchor or client-side route change, waitForNavigation() may resolve with null. That is expected and does not, by itself, indicate failure: there may be no new document response to return.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const [response] = await Promise.all([
  page.waitForNavigation({waitUntil: 'domcontentloaded'}),
  page.click('[data-route="reports"]'),
]);

console.log('navigation response:', response); // may be null
console.log('current URL:', page.url());
await page.waitForSelector('[data-page="reports"]');
console.log('title:', await page.title());

For a single-page application, verify both the expected URL and the rendered state. Do not write code that rejects every null response unless your workflow specifically requires a new document.

Use finite navigation timeouts

Puppeteer’s documented wait options use a 30-second timeout by default. Navigation-related operations such as goto, reload, setContent, and waitForNavigation are governed by the page’s default navigation timeout. You can set a limit explicitly:

page.setDefaultNavigationTimeout(45_000);

await page.goto('https://example.com', {
  waitUntil: 'domcontentloaded',
  timeout: 45_000,
});

Increasing the limit is appropriate when the target is predictably slow and you have evidence that it eventually reaches the selected condition. It does not repair a waiter attached after a click, an event that the page never fires, a selector that never appears, or a browser compatibility problem. Keep the timeout finite so failures produce a useful boundary and can be retried or reported.

A complete, defensive click-to-title example

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  page.setDefaultNavigationTimeout(30_000);

  await page.goto('https://example.com', {
    waitUntil: 'domcontentloaded',
  });
  console.log('start:', page.url());

  const navigation = page.waitForNavigation({
    waitUntil: 'domcontentloaded',
  });
  const click = page.click('a.my-link');
  const [response] = await Promise.all([navigation, click]);

  console.log('response:', response ? response.status() : null);
  console.log('url:', page.url());
  await page.waitForSelector('main');
  console.log('title:', await page.title());
} finally {
  await browser.close();
}

The explicit variables make the ordering obvious, while finally prevents a failed wait from leaving Chromium running. If the link triggers only History API routing, expect response to be null and use the URL and content checks as the success criteria.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Common failure modes and fixes

The waiter starts after the click

Symptom: the click appears to work, then waitForNavigation() times out. Fix: use the documented Promise.all pattern, with the waiter listed before the action.

load never arrives

Symptom: the page is visibly usable but the wait reaches its timeout. Fix: choose domcontentloaded, a selector, or an application-ready signal if that is what the task requires. Do not add multiple lifecycle events casually; all of them must fire.

A null response is treated as an error

Symptom: a client-side route or anchor change is reported as a failed navigation. Fix: accept a null response, then verify page.url() and the expected page state.

The timeout is increased indefinitely

Symptom: jobs take longer but still fail unpredictably. Fix: keep a finite timeout and determine whether the problem is a race, an impossible lifecycle condition, a missing selector, or genuinely slow infrastructure.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A different browser executable is used

Puppeteer guarantees operation with its bundled browser. A system-installed or otherwise different executable is a compatibility variable and is used at your own risk according to Puppeteer’s compatibility guidance. Record the executable path and browser version in the reproduction; first retest with the bundled browser.

The page is in a different frame

page.title() reads the main frame. If the visible content is inside an iframe, inspect that frame explicitly and wait for its state; changing the main-frame title call will not make an iframe’s title become the page title.

Build a useful minimal reproduction

  • Puppeteer package version (the official API pages identify version 25.12.0 for several interfaces; your installed version controls actual behavior).
  • Bundled versus custom browser executable and its version.
  • Operating system and headless or headed mode.
  • Exact URL class: full document navigation, redirect, anchor, or History API route.
  • The precise order of goto, click, navigation wait, selector wait, and title.
  • Every timeout and waitUntil value, plus the complete error text.
  • Logs immediately before and after each awaited operation and the URL at each point.

This information distinguishes a title-read problem from a navigation race or an environment mismatch. There is no single root cause that can be confirmed from the symptom alone.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your actual goal is a clean image or PDF of a URL rather than browser automation, ScreenshotNeo provides a one-request screenshot API. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Only clean shots are billed: bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the result with X-Page-Verdict and X-Billed headers. It also offers an MCP server for AI agents, with take_screenshot, get_page_info, and capture_pdf tools.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

See the complete parameter reference in the ScreenshotNeo documentation. cURL:

Best Value
The SQL Programming Language: .
  • Used Book in Good Condition
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}`);

Every plan includes the features: full-page lazy-image capture, CSS-selector element shots, dark mode, 12 device presets plus custom viewports, retina scale, PDF paper and page controls, HTML/CSS rendering, custom JavaScript and CSS, clicks, hidden selectors, selector/delay/network-idle waits, request and resource blocking, headers, cookies, user agents, authorization, timezone and geolocation, transparent backgrounds, resizing, configurable-TTL caching, signed links, asynchronous jobs with signed webhooks, bulk capture for 100 URLs per call, a usage API, OpenAPI, and compatible parameter names used by other screenshot APIs. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Sign up free.

FAQ

Does page.title() wait for the page to finish loading?

No. It reads the main frame’s current title and should be considered separate from navigation completion.

Should I always use networkidle?

No. Select the condition that matches the required outcome; ongoing requests can make network-idle conditions unsuitable for pages that are otherwise ready.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What should I do when a title is empty?

Check the correct frame and confirm that the application has rendered or updated its title before reading it; an empty string can be a valid current title state.

Frequently Asked Questions

Does page.title() wait for the page to finish loading?

No. It reads the main frame’s current title and should be considered separate from navigation completion.

Should I always use networkidle?

No. Select the condition that matches the required outcome; ongoing requests can make network-idle conditions unsuitable for pages that are otherwise ready.

What should I do when a title is empty?

Check the correct frame and confirm that the application has rendered or updated its title before reading it; an empty string can be a valid current title state.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Read next

Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.