ScreenshotNeo

BlogHow-to

How to Go Forward to the Next Page with Puppeteer

Use Puppeteer’s page.goForward() to replay the next browser history entry. Learn its return values, wait options, SPA behavior, and common errors.

By the ScreenshotNeo team4 October 20267 min read

Use await page.goForward() to move the current Puppeteer page to the next entry in its browser history. It returns a promise that resolves to the main resource response, or null for a same-page navigation. If there is no forward history entry, it throws. Puppeteer Page.goForward() reference.

This method replays browser history; it does not mean “open the next link” or navigate to a URL you already know. Use page.goto(url) for a known destination and page.goBack() to move backward in history.

1. Minimal example

await page.goForward();

It is only meaningful after the page has a forward history entry, such as after navigating from one page to another and then calling page.goBack(). Browser history belongs to the page’s browsing context; opening a new page does not automatically give it another page’s history.

2. Complete runnable example

The following CommonJS script launches Puppeteer, navigates through two URLs, goes back, and then goes forward. Install Puppeteer in a project first with npm install puppeteer; Puppeteer manages a compatible browser installation as part of its normal setup.

const puppeteer = require('puppeteer');

async function main() {
  const browser = await puppeteer.launch({ headless: true });
  try {
    const page = await browser.newPage();

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

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

    const backResponse = await page.goBack({
      waitUntil: 'domcontentloaded',
      timeout: 30_000,
    });
    console.log('Back response:', backResponse?.status() ?? 'same-page or no response');
    console.log('After going back:', page.url());

    const forwardResponse = await page.goForward({
      waitUntil: 'domcontentloaded',
      timeout: 30_000,
    });
    console.log('Forward response:', forwardResponse?.status() ?? 'same-page navigation');
    console.log('After going forward:', page.url());
  } finally {
    await browser.close();
  }
}

main().catch(error => {
  console.error('Puppeteer navigation failed:', error);
  process.exitCode = 1;
});

Save it as forward.js and run node forward.js. The URLs are illustrative; a site may redirect, require authentication, or behave differently. A null response is not itself a failure: it can mean the history transition changed the URL without loading a new document.

3. Options and return value

The documented signature is page.goForward(options?: WaitForOptions): Promise<HTTPResponse | null>. The optional object controls when Puppeteer considers the navigation complete and its timeout behavior. See the method reference and navigation timeout reference.

Value or option Meaning How to use it
Response object Main resource response for a document navigation; with redirects, the resolved navigation uses the last redirect response. Check response.status() when a response exists.
null Same-page navigation, such as a URL/history transition that does not load a new document. Do not treat null as proof that nothing happened. Check page.url() or an application-specific condition.
waitUntil Navigation lifecycle condition used by the wait options. Common choices include load, domcontentloaded, networkidle0, and networkidle2. Choose the least strict condition that represents readiness for your task.
timeout Maximum wait in milliseconds for navigation. Set it in the call or configure a page-wide navigation timeout with page.setDefaultNavigationTimeout(ms).

For example:

const response = await page.goForward({
  waitUntil: 'domcontentloaded',
  timeout: 20_000,
});

if (response) {
  console.log('HTTP status:', response.status());
} else {
  console.log('Same-page history transition; inspect the resulting page state.');
}
console.log('Current URL:', page.url());

Use networkidle0 or networkidle2 only when network quiet is a useful readiness signal. Applications with polling, analytics, streaming, or long-lived connections may never become idle. When the page’s real readiness is an element or application state, wait for that condition after history traversal instead of relying on network idleness.

4. History traversal, direct navigation, and SPAs

Goal Use
Replay the next entry in this page’s forward history await page.goForward()
Return to the previous entry await page.goBack()
Open a destination URL you know await page.goto('https://example.com/path')
Click a site’s “next” button and follow its behavior Interact with the button, then wait for navigation or the resulting application state.

Puppeteer considers a URL change to be navigation, including anchor navigation and History API changes. That definition supports single-page applications (SPAs), where a history entry may be replayed without fetching a whole new document. Therefore goForward() may resolve to null. After a same-page transition, wait for a route-specific selector or state if your next action depends on the rendered view. Puppeteer FAQ: what counts as navigation.

Do not call goForward() to advance a multi-page workflow unless the browser history actually contains the desired entry. A “Next” control can instead submit a form, update an SPA route, open a new tab, or use custom client-side state. Use the interaction that matches the site’s behavior.

5. Handling no history and other errors

The documented no-forward-entry case throws. If that state is expected, catch the error at the navigation boundary and choose a fallback that makes sense for your workflow. Puppeteer does not prescribe a universal fallback.

async function tryGoForward(page) {
  try {
    const response = await page.goForward({ waitUntil: 'domcontentloaded' });
    return { moved: true, response, url: page.url() };
  } catch (error) {
    return { moved: false, error, url: page.url() };
  }
}

This broad catch is suitable only when the caller will inspect or report the failure. In production code, avoid silently swallowing all exceptions: log or propagate unexpected browser, target-closed, and timeout errors so they can be diagnosed.

6. Troubleshooting

Symptom Likely cause Fix
Error because no history entry was found The page has not gone back from a later entry, or its forward history was replaced. Confirm the navigation sequence and page/context. If the destination is known, use goto(url); otherwise handle the missing entry.
The call times out The selected lifecycle condition did not occur before the navigation timeout. Choose an appropriate waitUntil, set a justified timeout, or wait separately for the app’s actual readiness condition.
The method resolves to null The history transition was same-page, so there was no main-document response. Check page.url() and wait for a route-specific selector or state rather than requiring an HTTP response.
URL changed but expected content is not ready URL changes can happen before application rendering is complete, especially in an SPA. After goForward(), wait for a meaningful locator or app state, such as await page.locator('[data-page="results"]').wait().
No visible movement in the browser The forward entry may be the same URL, or the site may restore state without an obvious URL change. Check page state and history sequence. Remember that the API is history traversal, not a command to load an arbitrary next URL.
A status check is skipped The code assumes a non-null response. Guard the response before calling status(); same-page navigation returns null.
Browser or target-closed error The browser/page was closed while navigation was pending, or the owning context was disposed. Keep the page alive until the awaited operation completes; close it in a finally block after the work.

7. Performance, reliability, and cost

goForward() is a browser history operation, not an API that guarantees a fresh network fetch. The browser may restore a document or replay a same-page transition; do not assume a particular cache or server-request behavior. Measure the workflow that matters to your application rather than inferring network activity from the method name.

  • Use the narrowest reliable wait condition. Waiting for every network connection to become idle can be slow or impossible on sites with persistent requests.
  • Set a finite navigation timeout appropriate to your environment. A timeout limits waiting; it does not make a missing history entry available.
  • Check the resulting URL and page state when correctness matters. A resolved promise alone does not prove that the desired route or content is present.
  • Close pages and browsers in cleanup paths to avoid keeping browser processes alive after failures.
  • Self-hosted Puppeteer costs depend on the compute and browser resources you operate. This API call has no separate Puppeteer navigation charge; infrastructure and any third-party services used by the page can still have costs.

8. Or skip the browser setup

If your task is to capture a URL as an image or PDF rather than traverse a user’s browser history, ScreenshotNeo provides a one-request website screenshot API and an MCP server. Puppeteer’s history method still fits when the sequence itself matters. See the ScreenshotNeo API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
  • Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
  • Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers report the page verdict and billing status.
  • An MCP server lets AI agents, including Claude, Cursor, and other MCP clients, take screenshots and capture PDFs.
  • The free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Every feature is on every plan.

Create a free ScreenshotNeo account for 1,000 screenshots a month, with no card required.

9. FAQ

Does goForward click a “Next” button?

No. It traverses the browser’s existing forward history entry. Use a locator to click a site control when that control defines the next step.

Can I use it on a page opened directly with goto?

Yes, if that page’s browsing context has a forward history entry. A direct navigation by itself does not guarantee one exists.

Does a successful call mean the server returned HTTP 200?

No. The method’s promise reports a response when there is a document response, but application-level success is separate. Inspect the response status when present and verify the page state you need.

Is goForward specific to Chrome?

It is a Puppeteer Page API. Puppeteer’s FAQ describes support for Chrome and Firefox from version 23 onward; verify your installed version and browser configuration in the official FAQ.

Official references