How to Capture a Mobile Website Screenshot with Puppeteer
Emulate a device or set a mobile viewport, then capture a viewport or full-page screenshot with Puppeteer. Includes runnable code and fixes for common issues.
To capture a mobile website screenshot with Puppeteer, configure mobile emulation before navigating, open the URL, then call page.screenshot(). Use a built-in device profile when you want its viewport and user agent together, or set a custom viewport when you need exact dimensions. The default screenshot captures the visible viewport; use fullPage: true for the entire document.
1. Install Puppeteer
In a new Node.js project, install Puppeteer:
npm install puppeteer
The package downloads a compatible browser by default. If your environment manages Chrome separately, see Puppeteer’s configuration and browser installation guidance before changing the default setup.
2. Capture with a built-in mobile device profile
Here is a complete ES module example using Puppeteer’s documented iPhone 17 Pro profile. Apply emulation before navigation: changing the page size can affect responsive layout, and some sites do not expect the viewport to change after loading.
import puppeteer, { KnownDevices } from 'puppeteer';
const url = process.argv[2] ?? 'https://example.com';
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
const device = KnownDevices['iPhone 17 Pro'];
if (!device) throw new Error('Device profile is not available in this Puppeteer version');
await page.emulate(device);
await page.goto(url, { waitUntil: 'networkidle2', timeout: 60_000 });
await page.screenshot({ path: 'mobile-screenshot.png' });
console.log('Saved mobile-screenshot.png');
} finally {
await browser.close();
}
Save this as screenshot.mjs and run node screenshot.mjs https://example.com. The KnownDevices registry can vary by Puppeteer version, so check the profile names available in your installed release if a key is missing. A device profile supplies device metrics and a user agent; page.emulate(device) is a shortcut for setting those together.
3. Choose a device profile or custom viewport
Use a device profile for a known model
Profiles are convenient when you want a documented device configuration applied as a unit. They include viewport metrics and user-agent information. The profile describes browser emulation; it does not make the capture equivalent to testing on a physical phone.
Set dimensions and mobile behavior yourself
For a design breakpoint or a nonstandard device size, use page.setViewport(). Width and height are CSS pixels. deviceScaleFactor controls emulated pixel density; isMobile enables mobile viewport behavior, and hasTouch enables touch input emulation.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.setViewport({
width: 390,
height: 844,
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true,
});
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
await page.screenshot({ path: 'custom-mobile.png' });
} finally {
await browser.close();
}
Set the viewport before goto(). Puppeteer documents that viewport changes resize the page and that applying mobile or touch settings may require a reload in some cases. For landscape, swap the width and height to the dimensions you intend to emulate.
4. Capture the viewport or the whole page
By default, page.screenshot() captures what is currently visible. To capture the full document height, set fullPage: true:
await page.screenshot({
path: 'full-mobile-page.png',
fullPage: true,
});
Full-page capture can produce a very tall image on long pages. If the site lazy-loads content as it scrolls, wait for or trigger the content you need before capture; a full-page screenshot does not guarantee every offscreen image or widget has finished loading.
5. Configure output and wait for the right content
| Need | Option or approach |
|---|---|
| Save to disk | Set path; the filename extension can determine the image type. |
| Choose format | Set type to a supported format such as png, jpeg, or webp where supported by the installed Puppeteer/browser version. PNG is the documented default. |
| Reduce JPEG size | Set quality for lossy formats such as JPEG; quality does not apply to PNG. |
| Wait for navigation | page.goto(url, { waitUntil: 'networkidle2' }) is useful for many pages, but pages with persistent network traffic may never become idle. |
| Wait for a specific UI | Use await page.waitForSelector('.main-content') or another selector that represents the content you need. |
| Wait a fixed interval | Use await new Promise(resolve => setTimeout(resolve, 1000)) only when a known delayed update requires it; a selector wait is usually more reliable. |
| Get bytes instead of a file | Omit path; the screenshot API returns image bytes. Use the returned data in your own storage or response handling. |
A robust capture can wait for a site-specific element after navigation:
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60_000 });
await page.waitForSelector('main', { timeout: 15_000 });
await page.screenshot({ path: 'mobile.webp', type: 'webp', fullPage: true });
Choose the wait condition for the target site. domcontentloaded avoids waiting for every subresource, while a selector wait confirms the relevant content exists. A fixed sleep is simple but can waste time or still be too short.
6. Capture from other clients
Puppeteer is a Node.js library, so the browser automation code above runs in JavaScript. If your workflow needs a screenshot service instead of managing Chromium, these runnable examples call ScreenshotNeo’s screenshot API. See the ScreenshotNeo API documentation for its parameters.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com \
-o shot.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
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('shot.webp', Buffer.from(await res.arrayBuffer())));
7. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Page looks like desktop | Navigation happened before mobile emulation, or only dimensions were changed. | Apply page.emulate() or the full custom viewport before goto(). Include isMobile: true when using a custom viewport. |
| Device profile is undefined | The installed Puppeteer version does not include that profile name. | Inspect the KnownDevices entries in the installed version or use a custom viewport. |
| Navigation times out | The site keeps connections open, loads slowly, or has a network dependency that does not settle. | Try domcontentloaded, set a suitable timeout, then wait for the specific content selector. Do not treat a timeout as proof the page is unusable; inspect whether the target content appeared. |
| Screenshot is blank or incomplete | Capture occurred before app rendering, fonts, images, or client-side content were ready. | Wait for a meaningful selector, and if needed wait for a known asynchronous update. Confirm the URL and page state before capture. |
| Lazy images are missing | Images load only after entering the viewport. | Scroll through the page to trigger lazy loading, wait for image loads, then capture. For very long pages, allow enough time for the scrolling and image requests. |
| Unexpected clipping | Default viewport capture was used, or the page layout differs at the chosen CSS dimensions. | Use fullPage: true for the whole document and verify the intended width and height before navigation. |
| Capture is blurry | Low emulated pixel density or scaling in a downstream display. | Set an appropriate deviceScaleFactor or use a device profile, and avoid enlarging the resulting image beyond its captured resolution. |
| Browser process fails in deployment | Chromium is missing, incompatible, or blocked by the runtime environment. | Install the browser version Puppeteer expects and follow Puppeteer’s deployment guidance for required system dependencies and sandbox settings. |
| Image file contains unexpected bytes or fails to open | An error response may have been saved as if it were an image, or format/path settings disagree. | Check the navigation and screenshot call, and use an explicit supported type when needed. |
8. Performance, reliability, and cost
A local Puppeteer capture has no per-screenshot service charge, but it uses your machine or server resources and requires you to install and maintain a compatible browser. Reusing a browser process for a batch can reduce startup overhead, but close each page and the browser when finished, and isolate jobs if pages are untrusted or resource intensive. Set navigation and selector timeouts so a stuck page does not hold a worker indefinitely.
For repeatable results, keep the browser and Puppeteer versions controlled, use the same viewport and wait condition, and account for dynamic page content, ads, personalization, and network variation. Emulation approximates a device configuration; it does not reproduce every hardware, operating-system, or browser behavior of a handset. Full-page and high-density captures increase image dimensions and memory use, so use viewport screenshots when the whole document is not needed.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. One GET request returns an image or PDF; its API also supports mobile device presets and custom viewport settings. For example:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
Cookie banners, popups, and chat widgets are removed before the shot; each step can be turned off. 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 a month with no card; paid plans start at $5 for 3,000. Read the API documentation and sign up for 1,000 free screenshots a month, with no card.
FAQ
Does Puppeteer take a mobile screenshot automatically?
No. Set a device profile or mobile viewport before opening the page.
Does a mobile screenshot prove a site works on a real phone?
No. Emulation is useful for responsive checks, but physical-device testing can reveal differences in hardware and browser behavior.
Should I use a device profile or custom dimensions?
Use a profile for a known device configuration; choose custom dimensions when you need a specific breakpoint or density.
Can I capture a mobile page as a PDF?
Puppeteer has PDF capture APIs for print-oriented output. A PDF follows print layout and pagination behavior, so it is a different result from a tall mobile screenshot.


