ScreenshotNeo

BlogHow-to

How to Display Puppeteer Screenshots in an HTML Page Without Saving Them Locally

Capture a Puppeteer screenshot in memory, turn it into a data URL, and display it in HTML without writing a local image file.

By the ScreenshotNeo team30 September 20269 min read

How to Display Puppeteer Screenshots in an HTML Page Without Saving Them Locally

You can display a Puppeteer screenshot without saving it locally by asking page.screenshot() for a Base64 string, adding the correct data:image/...;base64, prefix, and assigning the result to an HTML image element. Omit the path option so Puppeteer keeps the screenshot in memory.

const base64 = await page.screenshot({
  type: 'png',
  encoding: 'base64',
});

const image = document.createElement('img');
image.alt = 'Screenshot';
image.src = `data:image/png;base64,${base64}`;
document.body.append(image);

This pattern is useful for previews, test reports, dashboards and server-rendered pages where a temporary image file adds unnecessary storage and cleanup work. It only works when the page that displays the image receives the Base64 value. A variable in your Node.js process is not automatically available inside a separately served browser page.

How the in-memory flow works

  1. Puppeteer navigates to a target URL.
  2. page.screenshot() renders the page and returns image data.
  3. You select an explicit format such as PNG or JPEG.
  4. You either request a Base64 string or keep the default binary bytes.
  5. Your application sends that value to the HTML page.
  6. The browser decodes a data: URL and paints the image.

When path is omitted, Puppeteer does not save the screenshot to disk. With encoding: 'base64', the return value is a string. Without that option, the documented default is a Uint8Array. The screenshot guide and API reference document these behaviors in Puppeteer v25.12.0.

Puppeteer captures pixels in memory, and the browser renders them through a data URL.
Puppeteer captures pixels in memory, and the browser renders them through a data URL.

Complete Puppeteer example: render the screenshot in the same page

The following script creates a small HTML page, captures a different page, and inserts the screenshot into the document that will display it. In a real application, the display page is usually a route in your web app rather than a second local HTML file.

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch({
  headless: true,
});

try {
  const capturePage = await browser.newPage();
  await capturePage.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
  await capturePage.goto('https://example.com', { waitUntil: 'networkidle2' });

  const base64 = await capturePage.screenshot({
    type: 'png',
    encoding: 'base64',
    fullPage: true,
  });

  const displayPage = await browser.newPage();
  await displayPage.setContent(`
    <!doctype html>
    <html>
      <head>
        <meta charset="utf-8">
        <title>Screenshot preview</title>
        <style>
          body { margin: 2rem; font-family: system-ui, sans-serif; }
          img { max-width: 100%; height: auto; display: block; }
        </style>
      </head>
      <body>
        <h1>Screenshot preview</h1>
        <img id="preview" alt="Screenshot of example.com">
      </body>
    </html>
  `);

  await displayPage.evaluate((encoded) => {
    const image = document.querySelector('#preview');
    image.src = `data:image/png;base64,${encoded}`;
  }, base64);

  await displayPage.screenshot({ path: 'preview-of-preview.png' });
} finally {
  await browser.close();
}

The final displayPage.screenshot() is only there to demonstrate that the image was rendered. Remove it when the display page is your normal application page. The capture itself never writes a file.

Display a Base64 screenshot in an existing HTML page

If your server already returns an HTML document, send the encoded value to the template and construct the data URL in the markup:

<img
  alt="Screenshot"
  src="data:image/png;base64,PUT_BASE64_DATA_HERE"
>

A data URL has the form data:[media-type][;base64],<data>. The comma after base64 is required. Use image/png for PNG bytes and image/jpeg for JPEG bytes. The MIME type must match the actual screenshot format.

For an Express route, pass the value to a template engine:

app.get('/preview', async (req, res) => {
  const page = await browser.newPage();
  try {
    await page.goto('https://example.com', { waitUntil: 'networkidle2' });
    const screenshot = await page.screenshot({
      type: 'jpeg',
      quality: 82,
      encoding: 'base64',
    });
    res.render('preview', { screenshot });
  } finally {
    await page.close();
  }
});
<img
  alt="Screenshot preview"
  src="data:image/jpeg;base64,<%= screenshot %>"
>

Use your template engine’s normal escaping rules for surrounding HTML. The Base64 alphabet itself is safe as an attribute value, but keeping the value in a quoted attribute prevents accidental markup damage.

Keep binary bytes instead of Base64

Base64 is convenient when the image must be embedded directly in HTML, but it increases payload size and creates a very long attribute for large screenshots. Puppeteer can return binary data instead:

const bytes = await page.screenshot({
  type: 'png',
});

// Express or another Node HTTP framework:
res.type('png').send(Buffer.from(bytes));

This approach is often better when the browser can load a separate image endpoint:

<img src="/screenshots/latest" alt="Latest screenshot">

It avoids putting the complete image inside the HTML response and allows normal HTTP caching. If you need persistence, store the bytes in an object store or database and return them from an image endpoint. The capture step still does not need a local file.

Capture options that affect the embedded image

Option What it controls Practical use
type Image format, such as png or jpeg Set it explicitly so the data URL MIME type is correct.
encoding Return representation Use base64 for an inline data URL; omit it for binary bytes.
quality Lossy image quality Applies to formats other than PNG, such as JPEG.
fullPage Whether to capture the entire scrollable page Use true for a long-page preview.
clip A rectangular region to capture Use coordinates when only part of the viewport is needed.

For an element-only image, Puppeteer’s ElementHandle.screenshot() follows the same in-memory route:

const card = await page.$('.product-card');
if (!card) throw new Error('Product card was not found');

const base64 = await card.screenshot({
  type: 'png',
  encoding: 'base64',
});

await page.evaluate((encoded) => {
  document.querySelector('#result').src =
    `data:image/png;base64,${encoded}`;
}, base64);

Cross-process and client-server architectures

When Puppeteer runs in the same browser context as the page that displays the result, page.evaluate() can assign the value directly. More commonly, Puppeteer runs in a Node.js worker while a web server or frontend runs elsewhere. In that case, explicitly transport the value.

Return JSON containing Base64

app.get('/api/screenshot', async (req, res) => {
  const page = await browser.newPage();
  try {
    await page.goto(req.query.url, { waitUntil: 'networkidle2' });
    const base64 = await page.screenshot({ type: 'png', encoding: 'base64' });
    res.json({ mimeType: 'image/png', base64 });
  } finally {
    await page.close();
  }
});

The frontend can then build the source:

const response = await fetch('/api/screenshot?url=https%3A%2F%2Fexample.com');
const { mimeType, base64 } = await response.json();
document.querySelector('#preview').src = `data:${mimeType};base64,${base64}`;

Return an image response

For larger captures, return bytes directly and let the frontend use the endpoint as the image source. Set the response content type to the actual format and consider cache headers when the screenshot can be reused.

Or skip the browser setup

ScreenshotNeo provides a single GET request for a clean screenshot without maintaining a Puppeteer process. Its API can return PNG, JPEG, WebP or PDF. See the ScreenshotNeo API documentation for all parameters and response details.

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,
)
r.raise_for_status()
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}`);

ScreenshotNeo removes cookie and consent banners, newsletter popups and chat widgets before capture. Bot checks, blank pages, timeouts, failed loads and cache hits are not billed, and each response reports the result through X-Page-Verdict and X-Billed headers. Its MCP server provides take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients. The Free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 screenshots.

Create a free ScreenshotNeo account and start with 1,000 screenshots per month at no charge.

Data URL limits and browser performance

Data URLs are intended for short values. A screenshot can become large after increasing viewport dimensions, using fullPage, or selecting a high device scale factor. Browsers do not share one universal maximum data URL length, so a page that works in one environment may fail or become slow in another.

  • Prefer JPEG when photographic content and a smaller payload matter; use PNG for lossless UI details or transparency.
  • Use an appropriate viewport instead of capturing an unnecessarily wide page.
  • Resize or compress before embedding when your application permits it.
  • Use a binary image endpoint for large screenshots and let HTTP caching handle repeat views.
  • Do not put very large Base64 strings into logs, analytics events or URLs.

Base64 also consumes more memory than the original bytes because the encoded string is larger. For a high-volume service, close each page after capture, reuse a controlled browser pool, and avoid retaining screenshots longer than the response needs.

Reliability checklist

  1. Wait for the page state your screenshot requires. networkidle2 is useful for many pages, but pages with long polling may never become idle.
  2. Set a navigation timeout and catch errors so one target cannot hold a worker forever.
  3. Check that the element exists before calling ElementHandle.screenshot().
  4. Keep the selected image format and data URL MIME type synchronized.
  5. Close pages in a finally block and close the browser during process shutdown.
  6. Use a separate image response when the resulting HTML would be too large.

Troubleshooting common errors

The image icon appears, but no screenshot is shown

Check the prefix. It must include the media type, ;base64, and the comma: data:image/png;base64,. Also verify that the screenshot used type: 'png'. A JPEG payload with a PNG prefix can fail to decode.

The page shows the literal Base64 text

The value was inserted as text rather than assigned to img.src. Set the complete data URL on the image element or use a template expression inside the src attribute.

base64 is not a string

Set encoding: 'base64'. Without it, Puppeteer returns binary data. Convert those bytes to a response body, or request Base64 directly when an inline data URL is required.

The screenshot is blank or incomplete

Navigation may have finished before the application rendered its content. Wait for a known selector, a short application-specific delay, or a more suitable network condition. For lazy-loaded pages, scroll or use fullPage only after the required content is present.

Content Security Policy blocks the image

Inspect the active img-src policy. The page must permit data: sources for a data URL. If policy changes are not possible, return the screenshot from a same-origin image endpoint instead.

The HTML response is too large or slow

Switch from an inline data URL to an endpoint that sends the binary bytes with Content-Type: image/png or image/jpeg. Add caching where the screenshot is reusable.

Puppeteer cannot launch in production

Confirm that the deployment includes a compatible Chromium binary and the operating system dependencies required by your Puppeteer version. This issue is separate from Base64 encoding; first make sure a minimal browser.newPage() and page.goto() script works in the target environment.

FAQ

Does omitting path guarantee no disk writes?

It prevents Puppeteer from writing the screenshot file. Your operating system, browser cache configuration and application logging may still perform unrelated temporary I/O.

Can I embed a full-page screenshot?

Yes. Set fullPage: true, then use the same Base64 or binary response flow. Large full-page images are better served from an image endpoint.

Can I use WebP in a data URL?

Use a matching WebP MIME type such as image/webp if your Puppeteer version and target browser support the selected format. Keep the prefix synchronized with the actual bytes.

Should I use Base64 for permanent storage?

Usually no. Store binary bytes in an object store or database and serve them through an image endpoint. Base64 is most useful when the image is small and should travel inside one HTML or JSON response.

Can an iframe display the screenshot?

An iframe can display an HTML document containing the image, but an img element is simpler when the result is only an image. Cross-origin rules still apply to the page receiving the data.