Use Puppeteer to Screenshot a Website at a Mobile Viewport Size
Set a mobile viewport before navigation, then capture the page with Puppeteer. Learn device emulation, full-page options, troubleshooting, and a no-browser alternative.
To screenshot a website at a mobile viewport size with Puppeteer, set the viewport before navigating, open the URL, and save the page with page.screenshot(). Use page.setViewport() for exact dimensions, or page.emulate() when you also want a named device’s metrics and user agent.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.setViewport({
width: 390,
height: 844,
deviceScaleFactor: 1,
});
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
await page.screenshot({ path: 'mobile.png' });
} finally {
await browser.close();
}
The 390 × 844 dimensions are an example, not a universal phone size. Match the viewport to the layout or test case you need to reproduce. Puppeteer recommends configuring the viewport before navigation because sites may not expect a phone-sized resize after loading. See the official Puppeteer screenshot guide and setViewport API.
1. Install and run Puppeteer
In a Node.js project, install Puppeteer and create a JavaScript module. Puppeteer provides a browser automation API; the script below launches its configured browser, captures one page, and closes the browser even if navigation or capture fails.
npm install puppeteer
Save the first example as screenshot.mjs and run it with:
node screenshot.mjs
If your project uses CommonJS instead of ES modules, replace the import with const puppeteer = require('puppeteer'); and save the file with a .cjs extension. The remaining code is the same.
2. Set a precise mobile viewport
page.setViewport() specifies the page’s viewport width and height in CSS pixels. deviceScaleFactor controls the relationship between CSS pixels and output pixels: use 1 for standard scale and a larger value when you need a denser image. Set this before page.goto().
await page.setViewport({
width: 390,
height: 844,
deviceScaleFactor: 1,
});
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
await page.screenshot({ path: 'mobile.png' });
Viewport dimensions determine the visible layout area; they do not by themselves make the browser behave exactly like a physical phone. Choose the width and height that correspond to your design breakpoint, bug report, or visual regression case. A height of 844 means the initial viewport is that tall; it does not limit a full-page screenshot to 844 pixels.
3. Emulate a named device
Use page.emulate() when a device profile is more useful than manually specifying only dimensions. Puppeteer documents it as a shortcut for applying a device’s viewport and user agent. Emulate before navigation, and check the device names supported by your installed Puppeteer version because its device catalog can vary.
import puppeteer from 'puppeteer';
import { KnownDevices } from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.emulate(KnownDevices['iPhone 13']);
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
await page.screenshot({ path: 'device.png' });
} finally {
await browser.close();
}
If that device name is not present in your installed version, inspect the available KnownDevices entries or use setViewport() with explicit dimensions. Emulation is appropriate when the behavior under test depends on device metrics or user agent; exact viewport sizing is simpler when the dimensions alone are what matter. See Puppeteer’s Page.emulate() documentation.
4. Choose the screenshot area and format
By default, a screenshot captures the visible viewport and produces PNG output. The screenshot options let you capture the full document, specify a rectangular clip, and choose supported image formats. When a path is supplied, its extension can determine the output format.
// The visible viewport (default behavior)
await page.screenshot({ path: 'viewport.png' });
// The complete document, including content below the fold
await page.screenshot({ path: 'full-page.png', fullPage: true });
// A rectangular region in page coordinates
await page.screenshot({
path: 'region.png',
clip: { x: 0, y: 0, width: 390, height: 500 },
});
// JPEG output; quality applies to formats that support it
await page.screenshot({ path: 'mobile.jpg', type: 'jpeg', quality: 80 });
Use fullPage: true for a long page capture. Use clip when you need a specific rectangle rather than the full viewport or document. Quality is relevant to formats that support it and does not apply to PNG. For an element-only capture, locate the element and call its screenshot method:
const card = await page.$('.product-card');
if (!card) throw new Error('Product card was not found');
await card.screenshot({ path: 'product-card.png' });
Puppeteer attempts to scroll a hidden element into view before capturing it. See the official ScreenshotOptions API and screenshot guide.
5. Wait for the page state you need
The example uses waitUntil: 'networkidle2', which waits for a quiet network before continuing. Some sites keep requests open or load content after navigation, so a single navigation wait condition may not mean the particular component you need is ready. For a page with a known target, wait for that selector explicitly:
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('.page-ready');
await page.screenshot({ path: 'ready.png' });
You can also wait for a deliberate delay when the page has a known animation or delayed render, but prefer a readiness condition when one is available. If a capture needs content below the fold, verify that lazy-loaded content has actually appeared before relying on a full-page image.
6. cURL, Python, and Node.js options
Puppeteer is a Node.js library, so the native Puppeteer implementation is JavaScript. cURL and Python do not call Puppeteer’s API directly. They can still request an image from a screenshot service; for example, ScreenshotNeo offers a screenshot API with a single GET request. Its options and setup are documented at ScreenshotNeo documentation.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com \
--data-urlencode width=390 \
--data-urlencode height=844 \
-o mobile.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={
"access_key": "YOUR_API_KEY",
"url": "https://example.com",
"width": 390,
"height": 844,
},
timeout=90,
)
r.raise_for_status()
open("mobile.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://example.com',
width: '390',
height: '844',
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(({ writeFile }) =>
writeFile('mobile.webp', Buffer.from(await res.arrayBuffer()))
);
7. Or skip the browser setup
For a hosted capture, ScreenshotNeo accepts one GET request with the page URL and returns an image or PDF. Its screenshot API supports mobile viewport dimensions, among its capture options. Puppeteer remains useful when you need browser automation in your own process; the API avoids managing browser installation and lifecycle for a straightforward capture.
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com \
--data-urlencode width=390 \
--data-urlencode height=844 \
-o mobile.webp
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents use screenshot tools. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up for ScreenshotNeo and get 1,000 free screenshots a month, with no card required.
8. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| The page looks like a desktop layout | The viewport was set after navigation, or the chosen width does not cross the site’s mobile breakpoint. | Set the viewport before goto() and use the width required by the test. |
| The page reloads when the viewport changes | Some viewport changes, including changing mobile or touch properties, can trigger a reload. | Configure the final viewport or device emulation before navigation. |
| The screenshot is blank or missing content | Navigation completed before the needed content rendered, or a request failed. | Wait for a meaningful selector or page-specific ready condition before capture. |
| The capture stops at the first screen | The default screenshot is viewport-sized. | Set fullPage: true or use an element screenshot for a specific target. |
| A named device cannot be found | The installed Puppeteer version’s device catalog differs from the example. | Check KnownDevices in the installed package or use explicit viewport metrics. |
| The image is larger or smaller than expected | deviceScaleFactor changes output pixel density relative to CSS pixels. |
Set the intended scale explicitly and verify the resulting image dimensions. |
| Capture fails before saving | The browser may not have been closed on an earlier error, or the output directory may not exist or be writable. | Keep browser cleanup in a finally block and ensure the destination directory is available. |
9. Performance, reliability, and cost
A Puppeteer capture consumes time and resources to launch or reuse a browser, load the site, wait for readiness, and encode the image. For repeated captures in one process, consider reusing a browser and creating a fresh page for each job; always close pages and the browser when finished. Set practical navigation and operation timeouts for your workload, and use a page-specific ready signal when network-idle waiting is unreliable.
Results can vary with the target site’s content, network behavior, responsive breakpoints, and timing. A stable capture should fix the viewport, device scale, user agent or emulation profile, wait condition, and screenshot options. Puppeteer itself has no per-screenshot price in the cited API documentation; the operational cost depends on where and how you run the browser.
With ScreenshotNeo, the free tier provides 1,000 shots per month, then paid plans start at $5 for 3,000. Other listed plans are Growth $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000; yearly billing gives two months free. Every feature is available on every plan. Only clean shots are billed, and response headers report the page verdict and billing status.
10. FAQ
Does a mobile viewport make the screenshot look exactly like a phone?
It sets browser rendering dimensions. Use device emulation when the site’s behavior depends on the device profile or user agent; a viewport alone is not a physical-device test.
Should I use full-page capture for mobile screenshots?
Only when you need content beyond the initial viewport. For a screenshot of the screen as initially displayed, leave fullPage unset.
Can I capture a single component instead of the page?
Yes. Find the element and call its screenshot() method, which captures that element after Puppeteer tries to bring it into view.
Where can I check Puppeteer’s exact options?
Use the official screenshot guide, viewport API, emulation API, and screenshot options reference.


