How to Screenshot a Website at Mobile Viewport Dimensions with Puppeteer
Set a mobile-sized viewport before navigating, then capture a page with Puppeteer. Learn when to use device emulation, how to troubleshoot, and what to expect from the screenshot.
To screenshot a website at mobile viewport dimensions with Puppeteer, create a page, call page.setViewport() with the CSS-pixel width and height you need, navigate to the URL, and call page.screenshot(). Set the viewport before page.goto() so the page can respond to the intended size while it loads.
The dimensions below are an example, not a specification for a particular phone. Use viewport sizing when you need a particular width and height. Use Puppeteer device emulation when you need a named device’s viewport metrics and user agent as well.
1. Capture a page at a mobile viewport size
Install Puppeteer in a Node.js project, then save this as mobile-screenshot.mjs. The script uses a 390 × 844 CSS-pixel viewport, navigates to the target, writes a PNG, and closes the browser even if navigation or capture fails.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
// Example CSS-pixel dimensions. Choose values for your own target.
await page.setViewport({
width: 390,
height: 844,
deviceScaleFactor: 1,
});
await page.goto('https://example.com');
await page.screenshot({path: 'mobile.png'});
} finally {
await browser.close();
}
Run it with node mobile-screenshot.mjs. Replace the URL and dimensions as needed. Puppeteer’s viewport configuration uses CSS pixels; deviceScaleFactor controls the emulated device pixel ratio. A factor of 1 is useful when you want the output scale to match the CSS-pixel dimensions directly.
Choose the capture extent
The example captures a screenshot of the page using Puppeteer’s documented page.screenshot() method and saves it to a path. Decide whether your task needs the visible viewport or the whole document before adding screenshot options. A viewport screenshot is appropriate for checking what a visitor sees without scrolling; a full-document capture is useful for archiving or inspecting content below the fold. Check the current Puppeteer screenshot API documentation for the supported capture options and their behavior in your installed version.
2. Choose viewport sizing or device emulation
| Need | Use | What it configures |
|---|---|---|
| A specific width and height | page.setViewport() |
Viewport metrics, including width, height, and optionally device scale factor. |
| A known device preset | page.emulate(device) |
The device metrics and user agent for Puppeteer’s named device profile. |
page.emulate(device) is a shortcut for configuring the user agent and viewport together. Use it when the target is a particular device profile; do not assume that a generic mobile dimension pair reproduces every phone’s browser behavior. Puppeteer cautions that changing mobile or touch-related viewport properties can reload a page in some cases, which is another reason to configure emulation before navigating.
Example: emulate a named device
Puppeteer documents selecting a device from KnownDevices. This example uses the named iPhone 17 Pro preset shown in its documentation. Select the device that matches your actual requirement.
import puppeteer, {KnownDevices} from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.emulate(KnownDevices['iPhone 17 Pro']);
await page.goto('https://example.com');
await page.screenshot({path: 'device.png'});
} finally {
await browser.close();
}
Emulation changes browser-reported properties; it does not turn a desktop browser into a physical phone. If a site depends on hardware, a particular browser build, or behavior outside the emulated metrics and user agent, validate against the real target environment too.
3. Settle the page before capturing
A screenshot taken immediately after navigation may miss content that appears later. The basic example waits for Puppeteer’s navigation call to complete, but a site may still load images, hydrate client-side content, or show animations afterward. Choose a wait condition that matches the page and test objective: a meaningful selector becoming available, a deliberate short delay for known deferred content, or a suitable network-idle condition where the site supports it. Avoid waiting indefinitely for network inactivity on pages that maintain persistent connections or continuously fetch data.
- For a static page: navigation completion may be sufficient.
- For a client-rendered page: wait for a selector that represents the content you need to capture.
- For animation-heavy pages: allow the relevant animation to reach the state you intend to record.
- For repeatable visual checks: use the same viewport, browser setup, wait condition, and capture timing each run.
Choose viewport dimensions for the layout breakpoint under test. A width just above or below a responsive breakpoint can produce a different layout, so record the exact CSS-pixel width used in your script. Height also matters when sticky headers, viewport-relative sections, or visible fold position are part of the check.
4. Verify the configured viewport
page.viewport() reports Puppeteer’s configured viewport. It is useful for checking the value your script set, but it does not independently inspect the rendered page or prove that every responsive behavior matches a physical device.
const viewport = page.viewport();
console.log(viewport);
// For the example configuration, expect width: 390 and height: 844.
For debugging responsive layout, also inspect the actual page output or evaluate browser properties such as window.innerWidth. Compare the screenshot itself against the intended breakpoint and content state.
5. Troubleshooting Puppeteer mobile screenshots
| Symptom | Likely cause | Fix |
|---|---|---|
| The screenshot has a desktop layout | The viewport was set after navigation, or its width is outside the site’s mobile breakpoint. | Set the viewport before page.goto() and confirm the width in CSS pixels. |
| The page reloads after changing viewport settings | Mobile or touch properties can trigger a reload in some cases. | Set the viewport or emulate the device before navigation, then capture after the page has settled. |
| The page looks mobile but behaves differently from the target phone | Viewport dimensions alone do not set a device-specific user agent and all device characteristics. | Use a named device preset when appropriate, and validate hardware-dependent behavior on the target device. |
| Content is missing from the image | The content had not appeared when the screenshot was taken, or it was below the visible viewport. | Wait for the relevant selector or content state. Decide whether the task requires the viewport or whole-document capture and consult the installed version’s screenshot options. |
| Image dimensions seem larger than the viewport | A device scale factor greater than one can produce more image pixels per CSS pixel. | Set deviceScaleFactor explicitly and account for it when comparing image pixel dimensions. |
| The process stays open after an error | The browser was not closed when an operation threw. | Put capture work in a try block and close the browser in finally, as in the examples. |
6. Performance, reliability, and cost
Launching a browser and loading a page are usually the main work in a one-off capture. For repeated captures, reuse a browser process where your application design permits it, while isolating pages or contexts according to your concurrency and state requirements. Close pages and browsers deliberately so jobs do not accumulate resources.
For reliable output, make the viewport, device profile, target URL, wait condition, and capture extent explicit. Sites can vary by time, network response, personalization, and third-party content, so a screenshot is a record of one rendered state rather than a guarantee that future loads will look identical. Handle navigation and browser errors in the calling application, and retain enough job context to reproduce a failed capture.
Puppeteer itself does not define a per-screenshot service price in this workflow. Your costs depend on where and how you run Node.js and Chromium, including compute, memory, storage, and any browser infrastructure you provision. For occasional captures, account for setup and maintenance time as well as runtime cost.
Or skip the browser setup
If you need an image without managing Puppeteer and Chromium, ScreenshotNeo is a website screenshot API and MCP server. One GET request returns an image or PDF. It removes cookie banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots, and 1,000 screenshots per month are free with no card; paid plans start at $5 for 3,000.
Use the API’s viewport options when you need mobile dimensions; see the ScreenshotNeo API documentation for supported parameters and your API key.
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}`);
const image = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', image));
For mobile captures, add the viewport parameters supported by the API documentation. ScreenshotNeo also supports full-page capture, device presets and custom viewports, caching, and bulk capture. Each response identifies the page verdict and billing status in headers, so you can distinguish clean captures from failed or unbillable results.
Sign up for 1,000 free screenshots a month with no card.
FAQ
Are 390 × 844 the dimensions of a specific phone?
No. They are illustrative CSS-pixel values for the example. Choose dimensions from the layout or device requirement you need to reproduce.
Does setting a mobile viewport make Puppeteer use a phone user agent?
Viewport sizing configures dimensions. Use page.emulate(device) when you need Puppeteer’s named device metrics and user agent together.
Does page.viewport() confirm the page rendered correctly?
No. It reports the configured viewport. Inspect the rendered page or screenshot to check the actual layout.


