ScreenshotNeo

BlogHow-to

How to Convert a PDF to Images in Node.js

Render PDF pages to PNG or JPEG in Node.js with PDF.js, control dimensions, handle fonts and memory, and troubleshoot common conversion errors.

By the ScreenshotNeo team1 October 20267 min read

Use PDF.js to load the PDF, retrieve each page, create a viewport at the required scale, render the page into a Node.js canvas, and write the canvas as PNG or JPEG. Render pages one at a time and wait for each render to finish before reusing the canvas.

This guide uses pdfjs-dist and the canvas package. PDF.js documents the loading, page retrieval, viewport and rendering flow in its official documentation.

1. Set up the Node.js project

mkdir pdf-images
cd pdf-images
npm init -y
npm install pdfjs-dist canvas

The canvas package supplies the raster surface that PDF.js draws into. Its native dependencies vary by operating system, so read the package installation notes if your platform needs additional Cairo, Pango or JPEG libraries.

2. Convert every PDF page to PNG

Create convert-pdf.mjs:

import fs from 'node:fs/promises';
import path from 'node:path';
import process from 'node:process';
import { createCanvas } from 'canvas';
import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf.mjs';

const input = process.argv[2];
const outputDir = process.argv[3] ?? 'output';

if (!input) {
  console.error('Usage: node convert-pdf.mjs input.pdf [output-directory]');
  process.exit(1);
}

await fs.mkdir(outputDir, { recursive: true });
const data = new Uint8Array(await fs.readFile(input));

const loadingTask = pdfjsLib.getDocument({
  data,
  // Node.js does not use browser font faces by default.
  disableFontFace: true
});
const pdf = await loadingTask.promise;

for (let pageNumber = 1; pageNumber <= pdf.numPages; pageNumber += 1) {
  const page = await pdf.getPage(pageNumber);
  const viewport = page.getViewport({ scale: 2 });
  const canvas = createCanvas(Math.ceil(viewport.width), Math.ceil(viewport.height));
  const context = canvas.getContext('2d');

  await page.render({
    canvasContext: context,
    viewport
  }).promise;

  const output = path.join(outputDir, `page-${pageNumber}.png`);
  await fs.writeFile(output, canvas.toBuffer('image/png'));
  console.log(`Wrote ${output}`);
}

Run it with:

node convert-pdf.mjs document.pdf rendered-pages

The script creates one PNG per page. The scale of 2 doubles the PDF.js page viewport dimensions; increase it for more pixels or reduce it to limit memory use.

3. Choose an exact output width

A PDF page has a viewport width at scale 1. To target a particular pixel width, calculate the scale from that unscaled width, then request a second viewport at the calculated scale.

const targetWidth = 1600;
const baseViewport = page.getViewport({ scale: 1 });
const scale = targetWidth / baseViewport.width;
const viewport = page.getViewport({ scale });

const canvas = createCanvas(Math.ceil(viewport.width), Math.ceil(viewport.height));
await page.render({
  canvasContext: canvas.getContext('2d'),
  viewport
}).promise;

This preserves the page aspect ratio. The final height depends on the PDF page’s dimensions.

4. Write JPEG or WebP instead of PNG

PNG is lossless and works well for text, diagrams and line art. JPEG usually produces smaller files for photographic pages but introduces compression artifacts.

// JPEG
const buffer = canvas.toBuffer('image/jpeg', { quality: 0. nueve });
await fs.writeFile('page-1.jpg', buffer);

// WebP support depends on the installed canvas build.
const webp = canvas.toBuffer('image/webp', { quality: 0. nueve });
await fs.writeFile('page-1.webp', webp);

Replace 0. nueve with a JavaScript number such as 0.9 in your file:

const jpeg = canvas.toBuffer('image/jpeg', { quality: 0.9 });

5. Convert selected pages only

getPage(pageNumber) is one-based. Validate the requested range before rendering so an invalid page does not fail halfway through a batch.

const firstPage = 3;
const lastPage = Math.min(7, pdf.numPages);

if (firstPage < 1 || firstPage > pdf.numPages || firstPage > lastPage) {
  throw new Error(`Page range is outside 1-${pdf.numPages}`);
}

for (let pageNumber = firstPage; pageNumber <= lastPage; pageNumber += 1) {
  const page = await pdf.getPage(pageNumber);
  // create viewport, canvas and render as shown above
}

6. Return image buffers from a function

For an HTTP endpoint or a queue worker, return buffers instead of writing files.

import fs from 'node:fs/promises';
import { createCanvas } from 'canvas';
import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf.mjs';

export async function pdfPagesToPngBuffers(filename, scale = 2) {
  const data = new Uint8Array(await fs.readFile(filename));
  const pdf = await pdfjsLib.getDocument({ data, disableFontFace: true }).promise;
  const images = [];

  for (let pageNumber = 1; pageNumber <= pdf.numPages; pageNumber += 1) {
    const page = await pdf.getPage(pageNumber);
    const viewport = page.getViewport({ scale });
    const canvas = createCanvas(Math.ceil(viewport.width), Math.ceil(viewport.height));
    await page.render({ canvasContext: canvas.getContext('2d'), viewport }).promise;
    images.push({ pageNumber, buffer: canvas.toBuffer('image/png') });
  }

  return images;
}

7. Load a PDF from a URL or a buffer

For a local file, pass a Uint8Array as above. For a service that already downloaded the PDF, pass its buffer directly:

const pdf = await pdfjsLib.getDocument({
  data: new Uint8Array(pdfBuffer),
  disableFontFace: true
}).promise;

URL loading follows normal fetch and CORS rules. A remote server must allow the request, or download the file on your server first and provide the resulting bytes to PDF.js. See the PDF.js API reference for loading options.

8. PDF.js options that matter in Node.js

Option or concept When to use it
disableFontFace Node.js defaults this to true. Check font behavior when a document uses embedded or unusual fonts.
maxImageSize Set an upper pixel limit when processing large or untrusted PDFs to reduce memory risk.
CMaps and standard fonts Provide the required assets when a document depends on character maps or standard font data.
Worker and canvas factories Configure them when your deployment or bundler does not match the default Node environment.
Render parameters Use the viewport, transform and canvas context to control dimensions and drawing behavior.

Defaults and supported options can change with the PDF.js version, so check the API reference for the version installed in your project.

9. Download the source PDF with cURL

cURL does not rasterize PDF pages, but it is useful for obtaining a PDF before handing it to the Node.js converter:

curl -L "https://example.com/document.pdf" -o document.pdf
node convert-pdf.mjs document.pdf rendered-pages

10. Call the Node.js converter from Python

If the surrounding application is Python, keep the conversion implementation in Node.js and invoke it as a subprocess:

import subprocess

subprocess.run(
    ["node", "convert-pdf.mjs", "document.pdf", "rendered-pages"],
    check=True,
)

11. Troubleshooting

Cannot find module or native canvas build fails

Cause: pdfjs-dist or canvas is not installed, or the operating system lacks the native libraries required by canvas. Fix: run npm install, use a supported Node.js runtime, and follow the canvas package’s platform dependency instructions.

Blank or partially rendered pages

Cause: rendering was not awaited, the canvas was reused before the previous page finished, or a document asset could not be loaded. Fix: await page.render(...).promise for every page and render sequentially on each canvas.

Text appears with the wrong font

Cause: Node.js font defaults differ from browser defaults, or required font and CMap data is unavailable. Fix: inspect disableFontFace, provide the assets required by your PDF.js version, and verify the fonts used by the source document.

Out-of-memory errors

Cause: high scale, very large page dimensions, many canvases retained at once, or huge embedded images. Fix: lower the scale, process pages sequentially, release references after encoding, enforce an input-size and pixel limit, and consider maxImageSize.

Remote URL loading is rejected

Cause: the PDF host does not permit cross-origin requests or the URL requires authentication. Fix: download the PDF server-side with the required headers, then pass its bytes to getDocument.

Output files are unexpectedly large

Cause: PNG preserves every pixel and high scales multiply the pixel count. Fix: choose a lower scale, use JPEG for photographic content, or resize the output after rendering.

12. Performance, reliability and cost considerations

  • Render one page at a time unless you have measured a safe concurrency level for your memory limit.
  • Calculate scale from a target width instead of guessing dimensions.
  • Reject malformed, oversized or excessively long PDFs before allocating canvases.
  • Keep the PDF.js version and canvas implementation pinned and verify them when upgrading Node.js.
  • For repeat jobs, cache the source PDF hash and conversion settings so unchanged pages do not render again.
  • PDF.js itself has no per-page service charge; your costs come from compute, memory, storage and any infrastructure used to run the converter.

Or skip the browser setup

If what you need is a clean image of a public web page rather than rasterizing an arbitrary local PDF, ScreenshotNeo provides a single request:

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}`);

See the ScreenshotNeo API documentation for options. Cookie banners, newsletter popups and chat widgets are removed before the shot; bot checks, blank pages and failed loads are never billed. An MCP server lets AI agents take screenshots, and the free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.

Create a free ScreenshotNeo account.

FAQ

Does PDF.js convert a PDF without opening a browser?

Yes. In Node.js, PDF.js renders pages into a server-side canvas implementation; no browser window is required.

Should every page use the same scale?

Use one scale for consistent output density, or calculate a scale per page when every image must have a specific width.

Can I render only one page?

Yes. Call getPage with the one-based page number and encode that canvas without iterating over the rest of the document.

When should I choose a higher-level package?

Packages such as pdf-to-img and pdf2pic can reduce setup. Verify their current Node.js support, output behavior, native dependencies and maintenance before adopting them.