How to Capture Screenshots of JavaScript-Rendered Competitor Pages for SEO
Capture repeatable screenshots of JavaScript-rendered competitor pages with Playwright or Puppeteer, and learn what those images can—and cannot—tell you about SEO.
To capture a JavaScript-rendered competitor page, open it in a real browser engine with Playwright or Puppeteer, wait for a page-specific signal that the content you want is visible, then save a full-page or element screenshot. Record the URL, time, viewport, browser version, locale, and wait or interaction conditions so you can reproduce the observation. The image shows one browser’s rendering at one moment; it does not prove what Google crawled or indexed.
For a handful of pages and repeatable custom workflows, browser automation gives you control over navigation, waits, interactions, and output. For a site crawl with SEO signals beside rendered screenshots, Screaming Frog SEO Spider documents a JavaScript rendering mode with screenshot configuration. For Google’s view of a site you control, use Google’s debugging tools rather than treating a generic browser capture as a proxy.
1. Decide what you need to observe
Before writing code, identify the exact page state and question. A competitor page can vary by viewport, locale, consent choice, logged-in state, interaction, and time. A screenshot is useful evidence only when you know those conditions.
- Choose a representative public URL. Include query parameters if they affect the page. Avoid bypassing login, access controls, or technical restrictions.
- Define the state. Note whether the browser is logged out, which locale is selected, whether a consent banner was accepted, and whether a click or scroll reveals the content of interest.
- Choose a viewport. Capture desktop and mobile independently when responsive layout matters. Set dimensions deliberately rather than relying on machine defaults.
- Choose the target. A full-page screenshot helps review overall structure. An element screenshot is better for a specific hero, pricing table, or content block.
- Choose a readiness signal. Prefer a meaningful selector or known application state over assuming that a generic page-load event means all client-rendered content is ready.
For SEO work, keep visual inspection distinct from source inspection. A browser may display content assembled by JavaScript, but Google describes crawling, rendering, and indexing as separate stages; resources may be unavailable to its renderer, and rendering can be delayed. A screenshot cannot establish that Google saw or indexed the same content. See Google’s JavaScript SEO basics and its JavaScript rendering troubleshooting guidance.
2. Capture a rendered page with Playwright
Playwright supports page, full-page, and locator screenshots. The example below uses Chromium, waits for a page-specific heading, and saves a full-page PNG. Install Playwright and its browser first, then save this as capture.mjs and run it with Node.js.
npm install playwright
npx playwright install chromium
import { chromium } from 'playwright';
const url = 'https://example.com/competitor-page';
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
viewport: { width: 1440, height: 1000 },
locale: 'en-US',
timezoneId: 'UTC'
});
const page = await context.newPage();
try {
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });
// Replace this with a stable selector that identifies the content you need.
await page.locator('main h1').waitFor({ state: 'visible', timeout: 30000 });
await page.screenshot({ path: 'competitor-desktop.png', fullPage: true });
} finally {
await browser.close();
}
The wait is intentionally tied to a page element rather than a fixed sleep. Replace main h1 with a selector that indicates the relevant content has appeared. A visible heading may still appear before images or later sections finish loading; if those matter, wait for an additional selector or application-specific ready state.
Capture one element instead of the whole page
Use a locator screenshot when a specific section is the subject of comparison. The page must first reach the intended state.
const target = page.locator('[data-testid="pricing"]');
await target.waitFor({ state: 'visible', timeout: 30000 });
await target.screenshot({ path: 'competitor-pricing.png' });
If the site has no stable test identifier, use a CSS selector that uniquely identifies the section. Check that the locator matches the intended element before relying on a series of captures.
Capture desktop and mobile as separate observations
Make the viewport explicit for each capture. Responsive layouts can alter content order, visibility, and navigation; a scaled desktop screenshot is not a substitute for a mobile viewport.
const mobile = await browser.newContext({
viewport: { width: 390, height: 844 },
isMobile: true,
hasTouch: true,
deviceScaleFactor: 1,
locale: 'en-US',
timezoneId: 'UTC'
});
const mobilePage = await mobile.newPage();
await mobilePage.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });
await mobilePage.locator('main h1').waitFor({ state: 'visible', timeout: 30000 });
await mobilePage.screenshot({ path: 'competitor-mobile.png', fullPage: true });
await mobile.close();
For a useful before-and-after comparison, keep the operating system, browser version, browser settings, hardware, and headless mode consistent. Playwright notes these conditions can change rendering and produce visual differences. See Playwright’s visual comparisons guidance.
3. Capture with Puppeteer
Puppeteer is another browser automation option for teams working in Node.js. It automates Chrome and Firefox and supports screenshots and PDFs. The following example launches a browser, navigates to the URL, waits for a selector, and saves the full page.
npm install puppeteer
import puppeteer from 'puppeteer';
const url = 'https://example.com/competitor-page';
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 1000, deviceScaleFactor: 1 });
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });
await page.waitForSelector('main h1', { visible: true, timeout: 30000 });
await page.screenshot({ path: 'competitor-desktop.png', fullPage: true });
} finally {
await browser.close();
}
Check the maintained Puppeteer screenshot guide for current API details and browser setup. Browser automation APIs and installation requirements can change, so keep the package and browser versions recorded with your capture metadata.
4. Choose between code and an SEO crawler
| Need | Practical choice | Tradeoff |
|---|---|---|
| A few URLs or a tailored repeatable capture | Playwright or Puppeteer | Flexible waits and interactions; you maintain code and browser setup. |
| Crawl pages and inspect rendered screenshots alongside SEO data | Screaming Frog SEO Spider JavaScript rendering | Crawl-oriented workflow; configure the screenshot viewport and full-height behavior. |
| Find out how Google rendered your own page | Google Search Console and Google’s debugging tools | A generic browser screenshot does not reproduce Google’s separate rendering pipeline. |
Screaming Frog documents rendered screenshots when JavaScript rendering is selected, configurable desktop or smartphone Googlebot settings, custom window dimensions, scaling, and mobile/touch settings. Its resize-to-content option aims to capture the page up to 8192 pixels; without it, the screenshot is limited to the configured viewport. Consult the Screaming Frog configuration guide for the current options.
5. Make captures reproducible
Store context with every image, in a sidecar JSON file or a structured filename. Without metadata, a screenshot can be hard to interpret or recreate.
{
"url": "https://example.com/competitor-page",
"captured_at_utc": "2026-10-04T12:00:00Z",
"viewport": { "width": 1440, "height": 1000 },
"browser": "Chromium",
"browser_version": "record-installed-version",
"automation_tool": "Playwright",
"tool_version": "record-installed-version",
"locale": "en-US",
"timezone": "UTC",
"state": "logged out; consent not selected",
"wait_condition": "main h1 visible",
"interaction": "none"
}
Use the actual installed versions and capture time in your records. Repeat captures under controlled conditions and note intentional changes. If the page itself changes between runs, record that as part of the observation rather than assuming every pixel change came from your tooling.
6. Understand what the screenshot says about SEO
A rendered screenshot can help a person compare visible page structure, layout, headings, and content at a selected viewport. It cannot prove what a search engine crawled, rendered, or indexed. Google describes separate crawling, rendering, and indexing phases and explains that blocked resources or pages can prevent rendering. Not all crawlers execute JavaScript.
When the question is specifically “What does Google see?”, use URL Inspection and other Search Console debugging tools on a site you control or are authorized to inspect. Compare rendered output with the page’s source and crawlability. Google’s own documentation recommends inspecting rendered output to debug JavaScript SEO; an ordinary browser capture is only a visual observation, not Googlebot evidence.
7. Access and responsible-use checks
Publicly viewable content is not universal legal clearance for automated collection or republication. The 2019 Ninth Circuit hiQ Labs v. LinkedIn opinion addressed a narrow preliminary-injunction dispute involving public LinkedIn profile data and the CFAA; it does not establish permission to ignore site terms, defeat technical controls, republish copyrighted content, or collect personal information. See the court opinion.
Before systematic capture or publication, read the specific site’s terms and access instructions, avoid login or technical-control circumvention, limit request volume, and avoid collecting personal data you do not need. Applicable law and contractual obligations depend on the site, facts, and jurisdiction. The W3C’s screenshot guidance is specific to its own website and should not be generalized as permission for competitor captures.
8. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. One GET request takes a rendered page and returns an image or PDF. The call below captures a page as WebP; see the ScreenshotNeo API documentation for parameters and configuration.
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com/competitor-page \
-o competitor.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com/competitor-page"},
timeout=90,
)
r.raise_for_status()
open("competitor.webp", "wb").write(r.content)
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://example.com/competitor-page'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('competitor.webp', res);
- Cookie and consent banners, newsletter popups, and chat widgets are removed before the capture; each cleanup step can be turned off.
- Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed. Response headers report the page verdict and whether the request was billed.
- An MCP server lets AI agents use
take_screenshot,get_page_info, andcapture_pdf. - The free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 screenshots; every feature is on every plan.
Sign up free for 1,000 screenshots a month, with no card required.
9. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Screenshot shows a loading state or missing text | The capture started before client-side rendering reached the relevant state. | Wait for a page-specific selector or application state. Avoid relying on a short fixed delay. |
| Images or lower sections are missing | Images may be lazy-loaded until scrolling, or the page may populate sections later. | Scroll through the page before capture if the content requires it, then wait for the target images or sections. Recheck the full-page result. |
| Navigation or network-idle wait hangs | Some pages keep network connections active or load recurring requests. | Use a less restrictive navigation condition such as DOM content loaded, then wait for a meaningful selector. Set a finite timeout. |
| Screenshot differs between runs | Viewport, browser version, operating system, fonts, headless mode, content changes, or dynamic elements may differ. | Pin and record the environment, use the same viewport and state, and identify dynamic regions when interpreting comparisons. |
| Full-page image is clipped or unexpectedly tall | Tool behavior, page layout, or crawler screenshot height settings may limit the capture. | For browser automation, use the full-page option and inspect the output. In Screaming Frog, configure resize-to-content and account for its documented 8192-pixel maximum. |
| Navigation returns an error or challenge page | The site may be unavailable, rate-limiting, or presenting an access check. | Confirm the URL and availability, reduce request frequency, and respect the site’s access rules. Do not attempt to defeat technical controls. |
| Screenshot looks right but does not match Google’s result | A local browser screenshot is not Google’s rendering or indexing record. | For a site you control, inspect it with Search Console and Google’s debugging documentation. |
10. Performance, reliability, and cost
Browser automation requires a browser installation and compute for each capture. Reuse a browser process for batches when appropriate, but isolate page contexts when state must not leak between captures. Keep timeouts finite, capture only the needed page or element, and avoid unnecessary waits; these choices reduce wasted work without making readiness assumptions.
For repeatable tracking, schedule captures at a consistent cadence, record failures separately from successful images, and retry transient navigation failures with a limit. A retry should not silently replace the original result: preserve timestamps and note which attempt produced the image. Browser and operating-system changes can alter rendering, so treat upgrades as changes to the capture environment.
Self-hosted Playwright or Puppeteer has no per-screenshot service price in the cited documentation, but it uses engineering time and compute, and the reviewed sources provide no benchmark or cost comparison. An SEO crawler is a separate software choice; verify its current licensing and configuration directly with its vendor. ScreenshotNeo’s stated tiers are Free for 1,000 shots/month, Starter $5 for 3,000, 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. All features are included on every plan.
Frequently asked questions
Can a screenshot prove that a competitor’s page is indexed?
No. It records a browser rendering. Indexing requires separate evidence from a search engine’s systems, and a competitor’s indexing status is not established by your screenshot.
Should I use a full-page screenshot or an element screenshot?
Use a full-page image to review overall layout and content sequence. Use an element capture when you need a focused record of one section and want a smaller, easier-to-compare image.
Can I compare a mobile page with a desktop screenshot?
Capture each at its intended viewport and treat them as separate observations. Responsive pages can change content visibility and layout, so do not infer mobile behavior from a desktop image.
Does publicly visible content mean I can republish it?
No universal permission follows from visibility. Check the particular site’s terms and applicable rules, and get legal advice for systematic collection or publication when needed.


