ScreenshotNeo

BlogHow-to

How to Capture a Puppeteer Screenshot After a Page Redirects

Await Puppeteer navigation before capturing the page. Learn how to handle HTTP redirects, verify the final response, choose a readiness condition, and troubleshoot failures.

By the ScreenshotNeo team4 October 20266 min read

To capture a Puppeteer screenshot after an HTTP redirect, await page.goto() and then call page.screenshot(). Puppeteer follows the redirect chain during navigation; the response returned by goto() is for the last main-resource response. Choose a readiness condition that suits the page, and inspect the final response URL and status if you need to verify where navigation ended.

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  const response = await page.goto('https://example.com/old-path', {
    waitUntil: 'networkidle2',
  });

  console.log('Final response URL:', response?.url());
  console.log('Final response status:', response?.status());
  await page.screenshot({ path: 'after-redirect.png', fullPage: true });
} finally {
  await browser.close();
}

This is the usual pattern for a server-side HTTP redirect. The screenshot captures the page’s current rendered state after navigation and the selected wait condition have completed. Puppeteer’s Page.goto() API documents that the response after multiple redirects is from the last redirect. For screenshot options and examples, see the Puppeteer screenshots guide.

1. Capture the destination after an HTTP redirect

Navigate to the starting URL with page.goto(), await its promise, and take the screenshot afterward. Puppeteer follows HTTP redirects as part of the navigation. If the page redirects from an old path to a new one, the capture is of the resulting page, not a screenshot of the redirect response itself.

The example uses networkidle2, which is the wait condition in Puppeteer’s screenshot guide. It is a starting point rather than a guarantee that every application has finished rendering. If the page’s important content appears after navigation, wait for a specific selector or application condition before capturing.

goto() can return null for cases such as navigation to about:blank or a same-URL hash navigation. Optional chaining in the sample avoids assuming that a response always exists.

2. Choose when the page is ready

The redirect completing and the page being ready for a useful screenshot are related but separate concerns. waitUntil controls when Puppeteer considers the navigation complete; a site may continue rendering application content afterward.

Need Approach
Wait for Puppeteer’s screenshot-guide example condition Use waitUntil: 'networkidle2'.
Capture as soon as navigation reaches its selected lifecycle point Choose the appropriate waitUntil condition for the page’s loading behavior.
Wait for content that appears after navigation After goto(), wait for a page-specific selector or condition before calling screenshot().

There is no single readiness condition that fits every site. Pages with continuous background requests, client-side rendering, or delayed content may need a page-specific wait. Keep any extra wait bounded in your own application so a missing element does not hold the job indefinitely.

3. Check the final response and inspect redirects

Use the response from goto() to verify the final main-resource URL and status. A valid HTTP status such as 404 or 500 does not necessarily make navigation throw, so check the status explicitly when it matters to your workflow.

const response = await page.goto('http://example.com', {
  waitUntil: 'networkidle2',
});

if (response) {
  console.log('Final URL:', response.url());
  console.log('Final status:', response.status());

  const previousRequests = response.request().redirectChain();
  console.log('Redirected from:', previousRequests.map(request => request.url()));
}

await page.screenshot({ path: 'result.png', fullPage: true });

redirectChain() lists earlier requests in the redirect chain associated with the final response. With no redirects, the chain is empty. This is useful when diagnosing an unexpected destination or confirming that a starting HTTP URL upgraded to HTTPS. See the official HTTP request redirect-chain API and HTTP response API.

4. Capture after a click that navigates

If a click causes navigation, register the navigation waiter at the same time as the click. Waiting for the click to finish and only then attaching a navigation waiter can miss the navigation event.

const [response] = await Promise.all([
  page.waitForNavigation({ waitUntil: 'networkidle2' }),
  page.click('a.destination'),
]);

console.log('Destination:', response?.url());
await page.screenshot({ path: 'clicked-destination.png', fullPage: true });

Puppeteer also treats History API URL changes as navigation for waitForNavigation(). The same pattern applies: wait for the navigation and perform the action concurrently, then capture the resulting page. Refer to the official Page.waitForNavigation() API.

5. Screenshot options and output

The essential sequence is navigation, any page-specific readiness wait, then screenshot. Puppeteer’s page.screenshot() captures the current page and returns image data when no path is supplied; by default the result is a Uint8Array. With base64 output requested, it returns a base64 string.

  • path: save the screenshot to a file, as in the examples.
  • fullPage: true: capture the full page rather than only the visible viewport.
  • Omit fullPage or set it to false to capture the current viewport.
  • Use the returned image data instead of path when your application needs to upload or process the capture in memory.

For the complete supported screenshot options, consult the version of the Page.screenshot() API installed in your project.

6. Common problems and fixes

Symptom Likely cause Fix
The screenshot shows the old or intermediate page The capture ran before navigation completed, or the destination content renders later. Await goto(); add a wait for the destination’s specific content if needed.
The navigation waiter times out after a click The waiter was registered after the click, or the click did not cause navigation. Start waitForNavigation() and click() together with Promise.all(). If the action updates content without navigation, wait for that content instead.
response is null Some navigations, including about:blank and same-URL hash navigation, may not have a main-resource response. Handle the nullable response and do not call response methods without checking it.
Navigation resolves but the workflow treats the page as successful An HTTP error status can still produce a response without throwing. Check response.status() and decide how your application should handle that status.
The page looks incomplete despite a completed navigation Client-side rendering, delayed content, or the selected readiness condition does not match the page. Wait for a page-specific selector or condition before capturing.
The redirect destination is unexpected The starting URL redirected through one or more intermediate requests. Log response.url() and inspect response.request().redirectChain().

7. Performance, reliability, and cost

Every extra wait can increase capture time. Use a readiness condition that produces the content you need, then add a targeted wait only when the page renders important content later. Full-page screenshots can involve more page area than viewport captures, so use them only when the whole document is needed.

For reliable automation, close the browser in a finally block, as in the runnable example, so errors during navigation or capture do not skip cleanup. Treat navigation timeout, missing response, and non-success HTTP status as distinct outcomes in your application. Puppeteer’s behavior does not make a 404 or 500 equivalent to a navigation exception.

With a self-managed Puppeteer setup, account for the compute and operating work of running a browser process. The actual cost depends on your environment and capture volume; the documentation cited here does not publish a universal cost or performance benchmark.

8. Or skip the browser setup

If you need a screenshot after a redirect but do not want to manage a browser process, ScreenshotNeo accepts a URL in one API request and returns an image or PDF. Its API documentation describes the request options.

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}`);
  • Cookie banners are accepted like a visitor, then removed along with known consent platforms, newsletter popups, and chat widgets before the shot.
  • Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed; response headers identify the page verdict and billing status.
  • An MCP server lets AI agents use screenshot, page-info, and PDF-capture tools.
  • The free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 screenshots.

Sign up free for 1,000 screenshots a month, with no card required.

9. Frequently asked questions

Does page.goto() return the first or final redirect response?

For multiple redirects, Puppeteer documents that the returned response is from the last redirect. Use response.url() to inspect its URL.

Can I take a screenshot of the redirect response itself?

A server-side HTTP redirect response does not represent the rendered destination page. The usual screenshot workflow captures the page after navigation follows the redirect.

Will a 404 make page.goto() throw?

Not necessarily. Inspect the returned response status if your workflow needs to distinguish HTTP error responses.

What if a button changes the URL without a full page load?

Use waitForNavigation() alongside the click for navigation events, including History API URL changes, then capture after both promises resolve.