How to render a React page as a PNG image
Render a React page in a real browser, then capture the viewport, full page, or a component as PNG with Playwright or Puppeteer.
To render a React page as a PNG, first run the app in a browser, wait until the content you need is ready, then capture the page or a specific element with a browser automation library. Playwright and Puppeteer both support this workflow. JSX alone is not a rendered image: the browser must execute the app and paint its UI.
Use a viewport screenshot for the visible area, a full-page screenshot for the scrollable document, or an element screenshot for one component. The examples below use Playwright with Node.js; a Puppeteer option and API alternatives follow.
1. Choose the capture scope
| Goal | Capture | Typical use |
|---|---|---|
| What is currently visible | Viewport | Social previews, bug reports, a fixed-size card |
| The entire scrollable document | Full page | Long pages and reports |
| One React component | Element | Charts, cards, widgets, or isolated UI |
Viewport dimensions affect layout, line wrapping, and responsive breakpoints. Set the viewport before navigating or capturing so the result matches the intended screen size.
2. Install Playwright and start your React app
Install Playwright’s test package and its browser binaries:
npm install --save-dev @playwright/test
npx playwright install chromium
Start your React development server in another terminal. For example, many Vite projects use npm run dev, but use the command and URL configured by your project. Keep the server running while the capture script runs.
3. Capture a React page as PNG with Playwright
Create screenshot.mjs. Replace the URL, readiness selector, and output path for your application:
import { chromium } from '@playwright/test';
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({
viewport: { width: 1440, height: 1000 },
deviceScaleFactor: 1,
});
try {
await page.goto('http://localhost:5173', { waitUntil: 'domcontentloaded' });
// Prefer a selector that appears when the content to capture is ready.
await page.locator('[data-screenshot-ready]').waitFor({ state: 'visible' });
await page.screenshot({
path: 'react-page.png',
type: 'png',
fullPage: true,
animations: 'disabled',
});
} finally {
await browser.close();
}
Run it with node screenshot.mjs. For this example, add data-screenshot-ready to an element that only becomes visible once the page has the data and UI state you want to capture. If you do not control the app markup, wait for a stable visible element that signals readiness:
await page.getByRole('heading', { name: 'Dashboard' }).waitFor();
domcontentloaded only indicates that the initial HTML document has been parsed. It does not guarantee that React has finished fetching data, loading images or fonts, or completing delayed UI updates. Choose readiness conditions based on the page.
Viewport PNG
Omit fullPage or set it to false to capture the current viewport:
await page.screenshot({ path: 'viewport.png', type: 'png' });
Full-page PNG
Set fullPage: true to capture the full scrollable page:
await page.screenshot({ path: 'full-page.png', type: 'png', fullPage: true });
Very long pages can produce large images and take longer to encode or transfer. Lazy-loaded content may not appear unless it has been brought into view and loaded before the screenshot. If needed, scroll through the page before capturing, then wait for its images or other content to settle.
One component as PNG
Locate the rendered component and use its screenshot method:
const card = page.locator('[data-testid="report-card"]);
await card.waitFor({ state: 'visible' });
await card.screenshot({ path: 'report-card.png', type: 'png' });
Give the target a stable selector, such as a test ID or dedicated data attribute. A selector that matches multiple elements or a hidden element can make capture fail or target the wrong component.
4. Control dimensions, scale, and appearance
The browser viewport is measured in CSS pixels. Playwright’s screenshot scale option controls output pixel density: css produces one output pixel per CSS pixel, while device follows the device scale factor and can produce more pixels for high-DPI output. Choose intentionally because higher pixel counts increase file size.
await page.screenshot({
path: 'retina.png',
fullPage: true,
scale: 'device',
});
For a specific component, its rendered size determines the image bounds. If you need a fixed canvas, style or wrap the component at the desired dimensions before capture.
- Animations: Use
animations: 'disabled'for a settled capture. This can change transient animation states; omit it when the motion state is part of the desired image. - Transparent background: Use
omitBackground: truewhere transparency is appropriate, such as a component asset. Page backgrounds are usually better preserved for a faithful page image. - Masking: Playwright supports masks for selected locators, useful for visually variable regions in controlled screenshots.
- Screenshot styles: Playwright supports screenshot-specific styles. Use them to hide or adjust transient UI only when that produces the artifact you intend.
Check the installed Playwright version’s API documentation for the exact supported screenshot options and types.
5. Capture with Puppeteer instead
If your project already uses Puppeteer, the same browser-rendering approach applies. Install Puppeteer, start the React app, then save a screenshot:
npm install --save-dev puppeteer
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 1000 });
await page.goto('http://localhost:5173', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('[data-screenshot-ready]', { visible: true });
await page.screenshot({ path: 'react-page.png', fullPage: true, type: 'png' });
} finally {
await browser.close();
}
For a component screenshot in Puppeteer, select the element and call its screenshot method:
const element = await page.waitForSelector('[data-testid="report-card"]', { visible: true });
await element.screenshot({ path: 'report-card.png', type: 'png' });
Both Playwright and Puppeteer document browser-page and element screenshots. Pick the library that fits the automation stack and options your project needs; the available documentation does not establish a universal performance winner.
6. Make the capture reproducible
For repeatable output, keep the browser environment and page state stable:
- Use a fixed viewport and device scale factor.
- Use the same browser version, operating system, and headless settings for comparisons.
- Wait for application-specific data and image readiness rather than assuming one navigation event is enough.
- Disable or control animations and timestamps if they are not part of the intended result.
- Use stable test data and avoid capturing while the page is changing.
Playwright notes that screenshots can vary with host OS, browser version, settings, hardware, power source, and headless mode. Its screenshot assertions wait for consecutive screenshots to match before comparing a baseline, which can help with visual comparisons but does not remove environment differences. See the [Playwright visual comparisons guide](https://playwright.dev/docs/test-snapshots) and [Page API](https://playwright.dev/docs/api/class-page).
7. cURL, Python, and Node.js for a hosted React page
When the React app is already reachable by URL, a screenshot API can capture its browser-rendered page without maintaining browser automation setup in your application. The following examples use ScreenshotNeo’s API and a hosted page; replace the target URL with a public URL accessible to the capture service.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-o shot.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.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://stripe.com',
});
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('shot.webp', res);
The API examples save the default response as WebP. To request PNG or configure capture options, use the [ScreenshotNeo API documentation](https://screenshotneo.com/docs/). Protect your API key: keep it on a server or in a secret store, not in client-side React code. A browser-visible key can be copied and used by others.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. For an already hosted React page, one GET request returns an image; see the API documentation for PNG output and capture options.
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-o shot.webp
It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers indicate the page verdict and billing status. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots a 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.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Screenshot is blank or shows a loading state | The capture ran before React data or client rendering was ready. | Wait for a visible, app-specific ready selector or for the required data state. Do not rely on a generic navigation event alone. |
| Images or fonts are missing | They load asynchronously, are lazy-loaded, or are blocked by the environment. | Bring lazy content into view, wait for the relevant assets, and check the browser console and network requests. |
| Element screenshot times out | The selector is absent, hidden, or does not match the intended component. | Check the selector against the rendered DOM, wait for visibility, and use a stable test ID or data attribute. |
| Output is clipped | You captured the viewport when you needed the full document, or the element’s bounds differ from expectations. | Use fullPage: true for the document, or inspect the target element dimensions and layout. |
| PNG looks different between runs | Dynamic content, animations, fonts, browser versions, or rendering environments differ. | Stabilize data and environment, fix viewport and scale, and suppress transient animation where suitable. |
| Browser launch fails in a deployment environment | The browser binary or required runtime setup may not be available to the process. | Install the browser for the chosen library in the deployment environment and inspect its launch error. If browser management is not suitable there, capture the hosted URL through a screenshot API. |
| API key appears in a built React bundle | The key was placed in client-side code or a public environment variable. | Move the request to a trusted backend and rotate an exposed key. |
Performance, reliability, and cost
A capture must load and render the page before encoding the image. Full-page and high-density captures produce more pixels than a viewport or CSS-scale capture, so they can increase capture time, memory use, and output size. Capture only the needed scope and scale, reuse a browser process for batches when operating your own automation, and close pages and browsers reliably after work completes.
Reliability depends on page readiness, external resources, and a stable rendering environment. For automated jobs, set sensible timeouts, handle navigation and selector failures, and record which URL and capture settings produced an artifact. Do not treat a successful page navigation as proof that all app content finished rendering.
Self-hosted Playwright or Puppeteer has no per-image service charge in these examples, but you operate the browser runtime and its compute. A screenshot API trades that setup for service usage and plan limits. ScreenshotNeo lists a free tier of 1,000 shots per month and paid plans from $5 for 3,000; yearly billing gives two months free, and every feature is on every plan. Check the current plan details before choosing based on expected volume.
FAQ
Can I turn a React component into a PNG without opening a browser?
The documented Playwright and Puppeteer methods capture rendered browser pixels. A component definition by itself does not include its browser-rendered layout, fonts, styles, and runtime state.
Should I choose Playwright or Puppeteer?
Use the library already present in your automation stack unless a required option points you to the other. Both document page and element screenshots; the reviewed sources do not establish a universal speed advantage.
Why is the saved file larger than expected?
Check the capture dimensions, full-page setting, and device scale. More output pixels generally mean more image data to encode and store.
Can the screenshot include the page below the fold?
Yes. Use full-page capture, and ensure content that loads only when scrolled into view has been loaded before capturing.


