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

Run Puppeteer in a server-side Node.js Function, not in browser-side code. For the deployment pattern in Vercel’s guide, use puppeteer-core with a separately supplied Chromium binary; Vercel’s example template retrieves and extracts a Chromium archive at runtime, then caches the executable path in the warm function instance. That archive workflow is a documented option, not a requirement for every project. Match the browser and Puppeteer versions, check your project’s current function limits, and verify the deployed route in Vercel logs.

How the deployment fits together

A Puppeteer task needs a Node.js server process and a compatible Chromium executable. In a Vercel deployment, the Function runs the automation when a request reaches your server-side route. The browser binary must also be available to that Function, and the work must finish within the duration and resource limits for your project.

Vercel supports JavaScript and TypeScript Functions on Node.js; Node.js is the default when no additional runtime configuration is provided. See Vercel’s Node.js runtime documentation. Vercel’s Puppeteer guide describes a 250 MB Function bundle-size limit and recommends puppeteer-core with @sparticuz/chromium-min for its example. That figure is from the guide, whose search listing showed an update on November 10, 2025; verify the current limit and package compatibility before deployment. See the Puppeteer guide.

Choose how Chromium reaches the Function

Use the Vercel template’s archive pattern

Vercel’s accompanying template separates browser provisioning from the function code: Chromium assets are packaged into an archive during installation or build, made reachable to the deployed Function, downloaded and extracted as needed, and the resulting executable path is cached in memory for reuse by a warm instance. Consult the Vercel Puppeteer template for the implementation details.

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

This approach can keep the Function bundle lean, but it adds an asset location, retrieval and extraction steps, and version coordination to the deployment. The cited template illustrates one architecture; it does not establish that every Vercel project must use an archive. Any alternative still needs a compatible Chromium binary accessible at runtime and must fit the current deployment constraints.

Use a browser bundled for local development only as a development convenience

The standard puppeteer package downloads a browser, which is convenient for local work but can make a deployed function’s bundle too large for the constraint described in Vercel’s guide. For the guide’s deployment pattern, use puppeteer-core, which does not bundle its own browser, and provide Chromium separately. Do not assume a locally working browser install proves the deployed Function can find or execute its binary.

Build a server-side screenshot route

The following illustrates the route shape in a Next.js App Router project. It uses the package pair named in Vercel’s guide, takes the executable path and launch arguments from the Chromium package, and returns a PNG. Package APIs and compatibility can change, so check the installed versions against the current package documentation and template before adopting this code. Install the packages with npm install puppeteer-core @sparticuz/chromium-min; configure the template’s Chromium archive provisioning as appropriate for the selected package version.

Create app/api/screenshot/route.js:

import puppeteer from 'puppeteer-core';
import chromium from '@sparticuz/chromium-min';

export const runtime = 'nodejs';

let browserPromise;

async function getBrowser() {
  if (!browserPromise) {
    browserPromise = (async () => {
      // Set this to the archive location required by the package/template setup.
      const executablePath = await chromium.executablePath(
        process.env.CHROMIUM_ARCHIVE_URL
      );
      return puppeteer.launch({
        args: chromium.args,
        executablePath,
        headless: true,
      });
    })();
  }
  return browserPromise;
}

export async function GET(request) {
  const { searchParams } = new URL(request.url);
  const target = searchParams.get('url');
  if (!target) {
    return Response.json({ error: 'Provide a url query parameter.' }, { status: 400 });
  }

  let parsed;
  try {
    parsed = new URL(target);
  } catch {
    return Response.json({ error: 'The url must be an absolute URL.' }, { status: 400 });
  }
  if (!['http:', 'https:'].includes(parsed.protocol)) {
    return Response.json({ error: 'Only http and https URLs are allowed.' }, { status: 400 });
  }

  let page;
  try {
    const browser = await getBrowser();
    page = await browser.newPage();
    await page.goto(parsed.href, { waitUntil: 'networkidle2', timeout: 30000 });
    const image = await page.screenshot({ type: 'png', fullPage: true });
    return new Response(image, {
      headers: { 'Content-Type': 'image/png', 'Cache-Control': 'no-store' },
    });
  } catch (error) {
    console.error('Screenshot Function failed:', error);
    return Response.json({ error: 'Screenshot capture failed.' }, { status: 500 });
  } finally {
    if (page) await page.close().catch(() => {});
  }
}

The environment variable is deliberately a deployment-specific setting: use the archive URL and provisioning method required by the template and package version you have chosen. The code validates URL syntax and protocol, but a public screenshot endpoint also needs access control and protection against requests to internal or private network addresses; URL parsing alone is not an SSRF defense. Do not expose an unrestricted browser endpoint to the public internet.

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

This example keeps a browser promise in module scope so a warm instance can reuse the launched browser. A new page is created and closed for each request; the browser process is not closed after each capture. Instance memory is not shared across deployments or guaranteed to persist, so a cold instance must be able to provision and start Chromium again. Review the current package guidance for cleanup and runtime-specific requirements.

Deploy and verify the production function

  1. Install and configure. Add puppeteer-core and @sparticuz/chromium-min, then follow the chosen template or provisioning approach so the deployed function can retrieve its Chromium archive. Keep the binary location and credentials in server-side environment configuration.
  2. Run locally. Test the route with a controlled public page and confirm it returns a PNG. Local browser availability and production binary availability are separate concerns; verify each environment’s executable path.
  3. Deploy from the project root. Vercel’s CLI documentation shows vercel --prod for a production deployment. See the deploy command documentation.
  4. Exercise the deployed route. Request the production route with an encoded URL, for example https://YOUR-DEPLOYMENT.vercel.app/api/screenshot?url=https%3A%2F%2Fexample.com. Substitute your actual deployment host and target page. A successful request should return an image response with Content-Type: image/png.
  5. Inspect the deployment and Function logs. Confirm the intended branch and production deployment are active, then inspect logs for archive retrieval, browser launch, navigation, or timeout errors. Vercel’s function and limits documentation is at Function duration configuration and platform limits.

Set realistic time and resource limits

Browser startup, asset retrieval, navigation, and image or PDF generation all consume Function execution time. Vercel says duration defaults depend on plan and can be configured up to the plan’s limit; there is no single timeout number that applies to all projects. Check the current project configuration and plan limits rather than copying a timeout from an older example.

  • Use an explicit navigation timeout and choose a wait condition that matches the page. Waiting for network idle can be inappropriate for pages with persistent connections or frequent background requests.
  • Measure the whole request path, including cold browser startup and Chromium retrieval, not just the screenshot operation.
  • If work regularly exceeds the available duration, reduce unnecessary page work, use a more appropriate wait condition, or move the job to an execution environment designed for longer-running browser tasks.
  • Review memory and deployment bundle resources as well as duration. A smaller Puppeteer package does not remove the need to provision a functioning browser.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Common deployment failures and fixes

Chromium cannot launch

Check that the deployed Function can reach the configured archive, that extraction completes, and that the resulting path points to an executable. Confirm that the selected Chromium binary and Puppeteer version are compatible. Compare the deployed setup with the template’s archive and extraction workflow, and check Function logs for the actual launch error.

Deployment exceeds packaging constraints

Inspect the Function bundle and included dependencies. The standard puppeteer package can include a browser download; the Vercel guide’s lighter deployment pattern uses puppeteer-core with separately supplied Chromium. Recheck the current Vercel bundle limit and make sure the browser assets are provisioned using the intended strategy rather than accidentally copied into the function bundle.

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

The request times out or runs slowly

Separate time spent retrieving and starting Chromium from time spent navigating and capturing. Confirm the Function duration available to this project and plan, then adjust the route’s navigation timeout and wait condition to the work. A timeout setting does not raise the platform’s maximum execution allowance.

It works locally but not after deployment

Local machines often have a browser available that the deployed Function does not. Verify the production archive location, environment variables, extraction permissions, and executable path. Also confirm that the production deployment contains the expected dependency versions.

The old code is still serving

Open the intended deployment in Vercel, confirm the production alias and branch, and review its build and runtime logs. Run vercel --prod from the project root when you mean to create a production deployment; verify the new deployment rather than assuming a successful local build changed production.

When a managed screenshot endpoint is simpler

If the task is to return a website screenshot rather than run arbitrary Puppeteer automation, a screenshot API can avoid managing Chromium packaging and browser startup in your Function. ScreenshotNeo is a website screenshot API and MCP server: it accepts one GET request with a URL and returns an image or PDF. Its clean-shot behavior accepts consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; those steps can be turned off. Only clean shots are billed, while bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, with verdict and billing information in response headers. It also offers an MCP server for AI agents and a free allowance of 1,000 shots a month without a card.

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.

Or skip the browser setup

Use this cURL request to save a screenshot:

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

See the ScreenshotNeo API documentation for authentication and request options. Cookie banners, popups, and chat widgets are removed before the shot; bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots per month with no card, and paid plans start at $5 for 3,000. Create a free ScreenshotNeo account to get started.

Frequently Asked Questions

Can I use TypeScript instead of JavaScript for the Vercel Function?

Yes. Vercel supports both JavaScript and TypeScript Functions on its Node.js runtime.

Does this route let anyone screenshot any URL?

It accepts a target URL, but an Internet-facing endpoint should be access-controlled and protect against access to private or internal network addresses before it is used publicly.

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.

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.