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

Build the URL in your Node.js code, then pass the resulting string to await page.goto(url). For query parameters, use URL and URLSearchParams so spaces, ampersands and other reserved characters are encoded correctly; for path values, construct a path component rather than treating it like a query value.

The basic pattern

Puppeteer’s navigation method accepts a URL string. Define your variable, construct the destination, create a page, and call page.goto() with the finished URL.

import puppeteer from 'puppeteer';

const searchTerm = 'puppeteer page url';
const target = new URL('https://example.com/search');
target.searchParams.set('q', searchTerm);

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.goto(target.href);
} finally {
  await browser.close();
}

target.href is the complete URL, including the encoded query string. The browser receives that string exactly as it would receive a manually typed address.

Choose where the variable belongs

The correct construction method depends on whether the value is a path segment, a query parameter, or a relative URL.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
URL location Example destination Recommended construction Why it matters
Path segment https://example.com/users/42 Resolve a component as part of a path A slash in the value can otherwise create an unintended extra segment.
Query parameter https://example.com/search?q=puppeteer%20page%20url url.searchParams.set(name, value) Reserved characters such as & and spaces are encoded for you.
Relative input /docs/getting-started new URL(relative, base) The explicit base removes ambiguity about the host and scheme.
Complete absolute URL https://example.com/ Validate and pass the string directly page.goto() needs a navigable URL, normally including http:// or https://.

Query parameter: use URLSearchParams

This is the safest everyday case. Setting a parameter replaces an existing value with the same name and applies URL encoding.

const term = 'cats & dogs';
const pageNumber = 2;

const url = new URL('https://example.com/search');
url.searchParams.set('q', term);
url.searchParams.set('page', String(pageNumber));

await page.goto(url.href);

The resulting query contains an encoded representation of the space and ampersand. Do not concatenate an unescaped value such as '...?q=' + term; an ampersand inside the term would be interpreted as another parameter.

Path value: keep it a path component

A path identifier is not interchangeable with a query value. If an identifier can contain spaces, slashes, or other reserved characters, encode or validate it for the path position you have chosen. A simple identifier that is already restricted to safe characters can use a template literal:

const userId = '42';
const url = `https://example.com/users/${userId}`;
await page.goto(url);

For arbitrary user input, reject values that should never be identifiers, or encode the individual component before inserting it. Encoding an entire path string at once can also encode separators that are meant to remain separators, so decide first whether the value represents one component or several.

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

Relative URL: provide an explicit base

const relative = '/reports/weekly';
const destination = new URL(relative, 'https://example.com');
await page.goto(destination.href);

This pattern is useful when an application stores relative links. It prevents a relative value from silently being interpreted against an unexpected current location.

Complete Puppeteer example with reusable URL construction

The following script accepts a search term from the command line, adds optional filters, navigates, and reports the HTTP status when one is available.

import puppeteer from 'puppeteer';

function buildSearchUrl(term, pageNumber = 1) {
  if (typeof term !== 'string' || term.trim() === '') {
    throw new TypeError('term must be a non-empty string');
  }
  if (!Number.isInteger(pageNumber) || pageNumber < 1) {
    throw new RangeError('pageNumber must be a positive integer');
  }

  const url = new URL('https://example.com/search');
  url.searchParams.set('q', term);
  url.searchParams.set('page', String(pageNumber));
  return url;
}

const term = process.argv[2] ?? 'puppeteer page url';
const target = buildSearchUrl(term, 1);

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  const response = await page.goto(target.href, {
    waitUntil: 'domcontentloaded',
    timeout: 30_000
  });

  if (response) {
    console.log('status:', response.status());
    console.log('final URL:', page.url());
  } else {
    console.log('No main-resource response was returned.');
  }
} finally {
  await browser.close();
}

Run it with a quoted argument when the value contains spaces:

node search.js "puppeteer page url"

The explicit validation fails early instead of opening a malformed destination. The waitUntil and timeout settings are navigation policy choices; choose a readiness condition that matches the page you are automating.

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

Interpolation versus structured URL construction

Template literals are concise when a value is already valid for the exact component:

const slug = 'getting-started';
await page.goto(`https://example.com/docs/${slug}`);

Use the structured API when the value is external, optional, or likely to contain reserved characters:

const url = new URL('https://example.com/docs');
url.searchParams.set('topic', userInput);
await page.goto(url.href);

Structured construction also makes adding, replacing, and reviewing parameters straightforward. Node.js documents component-aware URL behavior; URL and URLSearchParams can apply different encoding rules, so do not assume that an encoding operation intended for a query is correct for a path.

Navigation results and HTTP failures

page.goto() resolves to the main-resource response in ordinary navigations. It can resolve to null for documented same-document cases, including about:blank or a navigation that changes only the hash. Always guard the response before reading its status.

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

A valid HTTP error status such as 404 or 500 does not, by itself, make navigation throw. If your workflow requires a successful response, check the status explicitly:

const response = await page.goto(target.href);
if (!response) {
  throw new Error('Navigation returned no main-resource response');
}
if (!response.ok()) {
  throw new Error(`Target returned HTTP ${response.status()}`);
}

Network failures, DNS errors, certificate problems, and a navigation timeout do reject the call. Catch those separately from an HTTP status so your logs explain whether the server answered or the browser could not complete the request.

Same-document navigation

A URL such as https://example.com/page#section may keep the same document and therefore not provide a new main-resource response. If you need to verify that the fragment was applied, inspect page.url() or query the page after navigation rather than relying on a response object.

PDF and headless-shell caveat

Puppeteer documents a limitation in headless-shell navigation for PDFs. If your target is a PDF and navigation behaves differently in that mode, use a supported headless mode or a PDF-oriented flow rather than assuming a normal HTML navigation response.

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

Common mistakes and fixes

Symptom Likely cause Fix
ProtocolError or an invalid URL error The value lacks a scheme or produces malformed syntax. Construct with new URL() and use an absolute base such as https://example.com.
Search text is truncated at an ampersand A query value was concatenated without encoding. Call searchParams.set() for each parameter.
A slash in an ID opens a different route A path value was treated as raw path text. Validate the ID or encode the single path component before insertion.
Navigation throws after a long wait The site did not reach the selected readiness condition before the timeout. Confirm the URL, increase the timeout only when justified, or choose a readiness event that matches the page.
No exception, but the page is a 404 or 500 HTTP error statuses are responses, not necessarily navigation exceptions. Check response.status() or response.ok().
response is null The navigation was same-document or otherwise had no new main resource. Handle null and inspect page.url() or page content.
The browser closes before work finishes browser.close() ran before asynchronous operations completed. Use await for navigation and processing inside a try/finally block.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Reliability, security, and performance practices

Validate untrusted destinations

If a user controls the host or complete URL, validate the scheme and allowed origins before passing it to a browser. Otherwise, a screenshot or scraping endpoint can become a server-side request forgery path. Prefer an allowlist of hosts when the application has a fixed set of destinations.

Reuse the browser, isolate pages

Launching Chromium is more expensive than opening a new page. For a batch of variables, launch once, create a page per isolated job (or reuse a page after clearing state), and close the browser in a shutdown path. Do not share cookies or authentication between tenants unless that is intentional.

Choose readiness deliberately

domcontentloaded usually returns sooner than waiting for every resource. Pages that render data after JavaScript may require a selector wait, a network-idle policy, or an application-specific readiness signal. A longer timeout cannot repair an incorrect URL or a page that never reaches the selected condition.

Log the constructed URL safely

Logging the final URL helps reproduce failures, but query strings can contain tokens or personal data. Redact secrets before writing logs, and avoid printing authorization values embedded in URLs.

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

Or skip the browser setup

If you only need an image or PDF of a URL, ScreenshotNeo provides a single HTTP request instead of requiring Chromium and Puppeteer. Its cleanup step accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers.

Use the API base shown in the ScreenshotNeo documentation:

curl -G "https://api.screenshotneo.com/v1/shot" 
  -d access_key=YOUR_API_KEY 
  --data-urlencode url=https://stripe.com 
  -o shot.webp

The same call from Python:

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

And from 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}`);
if (!res.ok) throw new Error(`ScreenshotNeo request failed: ${res.status}`);
const image = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', image));

ScreenshotNeo also offers an MCP server with take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. Its free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. The service supports PNG, JPEG, WebP, and PDF, plus options such as full-page capture with lazy images loaded, CSS-selector element capture, custom CSS or JavaScript, waits, request blocking, headers and cookies, device and viewport settings, caching, signed links, asynchronous webhooks, bulk capture of up to 100 URLs per call, and a usage API. Every feature is on every plan; yearly billing gives two months free.

Create a free ScreenshotNeo account to try the 1,000 monthly screenshots without a card.

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

Frequently Asked Questions

Can I pass a variable directly to page.goto()?

Yes. The argument is a string, so you can pass a variable containing a complete URL. Constructing that string with the URL API is safer when values are dynamic.

How do I add more than one query parameter?

Create a URL object and call searchParams.set() once for each parameter, then pass url.href to page.goto().

Should I use encodeURIComponent for a Puppeteer URL?

Use encoding appropriate to the component. URLSearchParams handles query values; a path segment needs path-specific validation or encoding. Do not apply one encoding strategy indiscriminately to an entire URL.

Why does page.goto() return null for a valid address?

Documented same-document navigations, such as about:blank or a hash-only change, can have no new main-resource response. Handle a null response and inspect the page URL or document instead.

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.

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.