How to Set Viewport and Device Scale Factor for SEO Screenshots in Puppeteer
Set Puppeteer’s viewport and device scale factor before navigation for repeatable mobile and desktop SEO screenshots. Learn which settings to choose and what screenshots can—and cannot—tell you about Google’s mobile crawl.
Set the viewport width, height, and deviceScaleFactor before navigating, then capture the page after it has loaded. The viewport dimensions define the CSS layout area you inspect; the device scale factor controls the emulated screen density. Google does not prescribe one universal Puppeteer viewport or scale factor for SEO screenshots.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.setViewport({
width: 390,
height: 844,
deviceScaleFactor: 1,
});
await page.goto('https://example.com', {
waitUntil: 'networkidle2',
timeout: 30000,
});
await page.screenshot({ path: 'page-mobile.png', fullPage: true });
} finally {
await browser.close();
}
})();
The 390 × 844 values are an example CSS viewport for checking a layout, not an SEO standard or a Google recommendation. Choose dimensions that represent the responsive layout you need to inspect.
1. What viewport and device scale factor mean
width and height in Puppeteer’s viewport are CSS pixels. They determine the size of the page’s visible layout area. deviceScaleFactor represents emulated screen density: a higher value can produce more physical screenshot pixels for the same CSS viewport.
That distinction matters when reviewing mobile pages. A phone’s physical pixel count is not the same thing as the CSS viewport width used by responsive layout. Google’s explanation of responsive design describes this difference between CSS pixels and physical pixels in its responsive design article.
For repeatable comparisons, use explicit width, height, and scale factor values. To approximate a known device profile, use Puppeteer’s device emulation instead; that can apply a device’s viewport and user agent together.
2. Set an explicit viewport before navigation
Puppeteer recommends setting the viewport before navigating because some sites do not expect a phone’s screen size to change. Its API also notes that setting the viewport can reload a page in some cases when isMobile or hasTouch is involved. See the official Page.setViewport API.
Complete runnable example
Install Puppeteer in a Node.js project, save the following as screenshot.js, and run node screenshot.js. The script sets the viewport first, navigates, and writes a full-page PNG.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
// Use CSS viewport dimensions for the responsive layout being inspected.
await page.setViewport({
width: 390,
height: 844,
deviceScaleFactor: 1,
});
const response = await page.goto('https://example.com', {
waitUntil: 'networkidle2',
timeout: 30000,
});
console.log('HTTP status:', response ? response.status() : 'no response');
await page.screenshot({ path: 'page-mobile.png', fullPage: true });
} finally {
await browser.close();
}
})();
The Page.screenshot() method captures the page after navigation; options such as path and fullPage control how the file is saved and whether the capture extends beyond the viewport. Refer to Puppeteer’s Screenshots guide and Page.screenshot API.
Viewport settings to know
| Setting | What it controls | How to choose it |
|---|---|---|
width |
CSS viewport width | Pick the narrow or wide layout you want to review. |
height |
CSS viewport height | Pick the visible fold for a viewport screenshot; a full-page capture extends beyond it. |
deviceScaleFactor |
Emulated screen density | Use a consistent value for comparable captures; increase it when you need a denser output image. |
isMobile |
Mobile viewport behavior | Use when emulating mobile behavior beyond merely choosing a narrow width. |
hasTouch |
Touch capability emulation | Enable when the page’s behavior depends on touch input. |
When using isMobile or hasTouch, set all viewport options before navigation. A viewport change can trigger a reload in certain cases, so changing it after loading can produce confusing results.
3. Choose between a controlled viewport and a device profile
For a screenshot matrix or regression check, set explicit dimensions and density. This makes each run easy to reproduce and compare. For a realistic known-device profile, use Puppeteer’s KnownDevices and page.emulate(device), which configures the device user agent and viewport. Puppeteer also advises doing this before navigation; see Page.emulate.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
const device = puppeteer.KnownDevices['iPhone 13'];
await page.emulate(device);
await page.goto('https://example.com', {
waitUntil: 'networkidle2',
timeout: 30000,
});
await page.screenshot({ path: 'page-device.png', fullPage: true });
} finally {
await browser.close();
}
})();
Known device names depend on the Puppeteer version in use; inspect puppeteer.KnownDevices in your installed version if the chosen name is undefined. Device emulation is convenient when the user agent and viewport should come from the same profile. Explicit settings are preferable when you need a stable custom matrix or want to isolate one variable at a time.
4. Build a useful SEO screenshot check
- Choose the responsive layout to inspect. Use CSS viewport dimensions relevant to your site’s mobile or desktop design. There is no universal SEO viewport to copy.
- Choose a consistent scale factor. Keep it fixed across runs when comparing layout changes. Use a higher density only when you need that output detail.
- Configure before navigation. Apply explicit viewport values or the device profile before calling
page.goto(). - Wait for the content that matters. Navigation completion is not always the same as a particular content block being rendered. For pages with dynamic content, wait for a meaningful selector or a bounded delay before capturing.
- Inspect the result and the page itself. Confirm important content, layout, and resources are available in the mobile experience. A local screenshot is a rendering check, not proof that Googlebot saw the same result.
Google says it uses the mobile version of a site’s content, crawled with the smartphone agent, for indexing and ranking. Its mobile-first guidance recommends keeping important content and resources available in the mobile experience and warns that primary content loaded only after user gestures may not be seen by Googlebot. A screenshot can help reveal a layout issue, but matching its dimensions does not ensure the same output as Google’s crawler; that follows from the separate roles of user agent, resources, and page behavior. Read Google’s mobile-first indexing best practices.
5. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Screenshot has desktop layout at a mobile width | Viewport was set after navigation, or the page’s responsive behavior depends on other mobile emulation settings. | Set the viewport before goto(). If needed, enable mobile or touch behavior, or use a known device profile. |
| Screenshot dimensions look larger than the configured viewport | deviceScaleFactor increases output pixel density; screenshot dimensions are not necessarily the CSS viewport dimensions. |
Compare the page’s CSS layout width separately from the raster image’s pixel dimensions. Keep scale factor consistent for comparisons. |
| Content is missing from the capture | The content loads asynchronously or only after scrolling, interaction, or a delayed request. | Wait for a content-specific selector. For lazy-loaded sections, scroll or trigger the relevant behavior before capturing, then verify that the content is also available without a user gesture if Google needs to discover it. |
| Page changes layout after the screenshot setup | Changing viewport properties can reload the page in some mobile or touch configurations. | Finish all emulation settings before navigation and avoid changing them mid-run. |
| Navigation times out on a page that appears usable | The site may keep network connections open, so a network-idle condition is never reached. | Use a less strict navigation wait such as domcontentloaded, then wait for a specific selector or bounded delay before capturing. |
| Known device lookup returns undefined | The name differs from the installed Puppeteer device list or the API changed between versions. | Inspect the installed KnownDevices keys and use an available profile, or define explicit viewport settings. |
6. Performance, repeatability, and cost
For repeatable visual review, hold the URL, viewport, scale factor, user agent, wait condition, and capture options constant. Record those inputs with the output so a changed screenshot can be traced to a changed setting or page response. Full-page captures can produce larger images and require more page rendering than viewport-only captures; use them when the content below the fold is part of the question.
A larger device scale factor can increase image dimensions and file size. Choose the density needed for inspection or downstream processing, and avoid generating oversized files when a normal-density screenshot answers the question. For dynamic pages, wait for the content you need rather than relying on an open-ended idle condition, which can make capture time unpredictable.
Running Puppeteer means managing a browser process, its dependencies, navigation waits, and output storage. That can be appropriate for custom browser workflows or local checks. If captures are occasional and you do not want to operate the browser setup, a screenshot API can make the request and return the image in one step.
7. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server for developers. One GET request returns a PNG, JPEG, WebP, or PDF. See the API documentation for its parameters.
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 bytes = Buffer.from(await res.arrayBuffer());
require('node:fs').writeFileSync('shot.webp', bytes);
ScreenshotNeo can accept cookie or consent banners and remove more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server gives AI agents tools for screenshots, page information, and PDF capture. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Sign up for 1,000 free screenshots a month with no card.
FAQ
What viewport size should I use for mobile SEO screenshots?
Use the CSS dimensions that let you inspect the responsive layout relevant to your site. Google does not define a universal screenshot width and height.
Does deviceScaleFactor affect SEO?
It controls emulated screen density for the capture; it is not a ranking setting. The screenshot is useful for visual inspection, while Google’s crawling and rendering determine what it can index.
Should I use a real device profile or custom dimensions?
Use a known profile when its user agent and viewport are useful together. Use explicit dimensions and density for a controlled, repeatable screenshot matrix.
Does a matching mobile screenshot prove Google sees the same page?
No. A local capture does not establish what Googlebot fetched or rendered. Verify mobile content and crawlable resources independently using Google’s guidance.


