ScreenshotNeo

BlogHow-to

How to Use PDF.js in React

Render PDFs in React with React-PDF or pdfjs-dist, configure a version-matched worker, and fix common asset and rendering errors.

By the ScreenshotNeo team30 September 202610 min read

How to Use PDF.js in React

To use PDF.js in React, install react-pdf, configure its PDF.js worker in the same module that imports Document and Page, then render the file with those components. For lower-level control, install pdfjs-dist and use its display API: load the document, get a page, create a viewport, size a canvas, and await the render task. In either approach, serve the app over HTTP and keep the worker version matched to the installed PDF.js package.

Most React applications should start with React-PDF. It wraps PDF.js in React components and callbacks while leaving you in control of the surrounding viewer interface. Use pdfjs-dist directly when you need to own the canvas lifecycle and rendering behavior. PDF.js itself has core, display, and viewer layers: core parses PDF data, display exposes document and rendering APIs, and viewer is a user interface built on the display layer. Mozilla recommends reskinning or building on the viewer rather than copying it unchanged. See the PDF.js project and the React-PDF documentation.

1. Install and configure React-PDF

Install the React wrapper:

React-PDF coordinates the document, worker, and rendered page layers.
React-PDF coordinates the document, worker, and rendered page layers.
npm install react-pdf

The worker is a separate script. Without it, PDF.js may try to use a fake worker or report that it could not set up its worker. The recommended setup uses a URL resolved by the bundler. Put the worker assignment in the same file as the imports and component that use React-PDF; a separate module can run in an order that lets another import overwrite the setting.

import { useState } from 'react';
import { Document, Page, pdfjs } from 'react-pdf';
import 'react-pdf/dist/Page/AnnotationLayer.css';
import 'react-pdf/dist/Page/TextLayer.css';

pdfjs.GlobalWorkerOptions.workerSrc = new URL(
  'pdfjs-dist/build/pdf.worker.min.mjs',
  import.meta.url,
).toString();

export default function PdfViewer() {
  const [numPages, setNumPages] = useState<number>();
  const [pageNumber, setPageNumber] = useState(1);
  const [error, setError] = useState<string>('');

  function onLoadSuccess({ numPages }: { numPages: number }) {
    setNumPages(numPages);
    setPageNumber(1);
    setError('');
  }

  function onLoadError(err: Error) {
    setError(err.message);
  }

  return (
    <section>
      <Document
        file="/documents/guide.pdf"
        onLoadSuccess={onLoadSuccess}
        onLoadError={onLoadError}
        loading={<p>Loading PDF…</p>}
        error={<p>This PDF could not be opened.</p>}
      >
        <Page pageNumber={pageNumber} />
      </Document>
      {error && <p role="alert">{error}</p>}
      {numPages && (
        <nav aria-label="PDF pages">
          <button
            type="button"
            disabled={pageNumber <= 1}
            onClick={() => setPageNumber((p) => p - 1)}
          >Previous</button>
          <span> Page {pageNumber} of {numPages} </span>
          <button
            type="button"
            disabled={pageNumber >= numPages}
            onClick={() => setPageNumber((p) => p + 1)}
          >Next</button>
        </nav>
      )}
    </section>
  );
}

This example is a TypeScript React component; in plain JavaScript, remove the type annotations on useState and the callback argument. The Document component loads the file and reports the page count. Page renders the requested page. The CSS imports support annotation and text layers; keep them if links, annotations, or selectable text should be visible. Remove them only when those layers are not needed.

Worker options

React-PDF documents several ways to make the worker available:

  • Bundler URL (recommended starting point): resolve pdf.worker.min.mjs with new URL(..., import.meta.url) as above. This works with modern bundlers that process the asset reference.
  • Copy the worker: copy the installed package’s pdf.worker.mjs into a public/output directory and set workerSrc to its served path. This gives you control over deployment paths; ensure the copied worker is updated whenever pdfjs-dist changes.
  • Version-matched CDN: set workerSrc to //unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs, interpolating the runtime pdfjs.version. This avoids bundling the worker but makes PDF rendering depend on network access to the CDN.

For older browser targets, React-PDF documents using the /legacy/build/ worker path. A legacy worker alone does not guarantee support: the application may also need polyfills and bundler transpilation. The current React-PDF README describes its 11.x branch as requiring React 19 or later and Node.js 22.13.0 or later, with minimum browser versions including Chrome 125 and Safari 18 (iOS 18). These requirements change; check the README for the release you install.

2. Load files and configure auxiliary assets

file can identify a same-origin URL, a URL permitted by CORS, or supported PDF data. For authenticated or cross-origin documents, configure the file request deliberately. React-PDF accepts a file object with URL and request options, for example:

Some PDFs need cMap, standard font, or WASM assets alongside the worker.
Some PDFs need cMap, standard font, or WASM assets alongside the worker.
<Document
  file={{
    url: 'https://files.example.test/manual.pdf',
    withCredentials: true,
  }}
  onLoadSuccess={({ numPages }) => setNumPages(numPages)}
>
  <Page pageNumber={1} />
</Document>

Cross-origin requests still require the document server to allow the requesting origin. Credentials do not bypass CORS. Avoid placing long-lived secrets in client-side code; a browser user can inspect requests and bundled configuration.

Some PDFs need resources beyond the worker and the PDF file. React-PDF supports an options object on Document. Keep it stable outside the component or memoize it so a fresh object is not passed on every render.

const pdfOptions = {
  cMapUrl: '/cmaps/',
  standardFontDataUrl: '/standard_fonts/',
  wasmUrl: '/wasm/',
};

// In the component:
<Document file="/documents/manual.pdf" options={pdfOptions}>
  <Page pageNumber={1} />
</Document>

Copy the corresponding directories from pdfjs-dist into the public output at those paths, or serve them from a suitable CDN and use stable URLs. cMaps matter for some non-Latin character encodings; standard font data supports PDFs that rely on standard fonts; WASM resources may be needed for JPEG 2000 content. Configure only the resources your documents require, and verify that deployment serves them at the exact paths.

3. Render directly with pdfjs-dist

When you want direct control, install the display package:

npm install pdfjs-dist

Here is the essential browser-side sequence. This example assumes your bundler can resolve the worker asset reference and your PDF is served from the application origin.

import * as pdfjsLib from 'pdfjs-dist';

pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
  'pdfjs-dist/build/pdf.worker.min.mjs',
  import.meta.url,
).toString();

export async function renderPdfPage(
  canvas: HTMLCanvasElement,
  pdfUrl: string,
  pageNumber = 1,
  scale = 1.25,
) {
  const context = canvas.getContext('2d');
  if (!context) throw new Error('Canvas 2D context is unavailable');

  const loadingTask = pdfjsLib.getDocument(pdfUrl);
  const pdf = await loadingTask.promise;
  const page = await pdf.getPage(pageNumber);
  const viewport = page.getViewport({ scale });

  canvas.width = Math.ceil(viewport.width);
  canvas.height = Math.ceil(viewport.height);
  canvas.style.width = `${Math.ceil(viewport.width)}px`;
  canvas.style.height = `${Math.ceil(viewport.height)}px`;

  const renderTask = page.render({ canvasContext: context, viewport });
  await renderTask.promise;
  return { pageCount: pdf.numPages, pageNumber };
}

Call this after the canvas exists in the DOM, such as from a React effect using a canvas ref. In a full component, cancel or finish the previous render task before starting another render on the same canvas, and handle rejected loading/render promises so navigation does not create unhandled errors. Validate pageNumber against pdf.numPages. Scale controls display dimensions; it does not change the source PDF.

For a direct PDF.js integration using Webpack, Mozilla’s setup guidance describes bundling the worker separately; its Webpack example sets GlobalWorkerOptions.workerSrc to the emitted worker bundle. The package also provides a Webpack entry for worker autoconfiguration. Bundler behavior differs, so follow the setup supported by your bundler and installed package version rather than mixing worker recipes.

4. Choose a rendering strategy

Need Recommended approach Trade-off
Show pages in a typical React screen React-PDF Document and Page Less canvas lifecycle code; use callbacks and props for application behavior.
Custom canvas lifecycle or integration Direct pdfjs-dist display API More control, but you own loading state, errors, page state, resizing, and cancellation.
Selectable text and links React-PDF with text and annotation layers enabled Include the corresponding styles and test layer alignment at your chosen scale.
Older browser support Legacy worker plus any needed polyfills/transpilation More deployment complexity; confirm the target browser matrix for your package release.

PDF.js renders pages to browser surfaces; it is not by itself a complete document management product or a finished application interface. Add accessible page controls, loading states, keyboard behavior, and error handling around the renderer that suit your product.

5. Troubleshooting common errors

Symptom Likely cause Fix
“Setting up fake worker failed” or worker setup error The worker URL is missing, not emitted, or blocked. Set workerSrc in the component module; inspect the browser Network panel for the worker request and confirm the path returns JavaScript.
API version and worker version do not match Worker file came from a different pdfjs-dist release, often due to a stale copied file or pinned CDN URL. Regenerate/copy the worker from the installed package or use a URL containing pdfjs.version. Keep package and worker deployment updates together.
It works in development but not after deployment Public asset base path differs, or worker/cMap/font/WASM files were not included in the build. Check production network requests and configure paths for the deployed base URL. Confirm all required directories are copied.
PDF fails from file:// PDF.js does not enable its worker for file URLs. Serve the app through an HTTP development server or deploy it through a web server.
Cross-origin document request fails The PDF host does not allow the app origin through CORS, or credential policy is mismatched. Configure CORS on the document host, use the correct credentials setting, or serve the PDF through an authorized same-origin endpoint.
Some characters are missing or garbled The document needs cMaps or the cMap URL is wrong. Package pdfjs-dist/cmaps and pass a stable cMapUrl such as /cmaps/.
JPEG 2000 content fails Required decoder WASM assets are absent. Serve the package’s wasm directory and configure wasmUrl.
Links or selectable text are missing Annotation/text layers or their styles are absent. Import React-PDF’s AnnotationLayer.css and TextLayer.css; ensure the relevant layers are not disabled.
Repeated loads or flicker after React rerenders Unstable options/file objects or rendering effects restart on each render. Move options to module scope, memoize changing objects, and clean up/cancel direct render tasks when inputs change.

6. Performance, reliability, and cost

Rendering cost grows with page dimensions and scale: a higher scale creates a larger canvas and consumes more memory. Avoid rendering every page at high resolution at once for a long document. Render the visible page and nearby pages, release canvases when they leave the view, and use a lower preview scale when users need to scan pages before zooming. Measure on the devices and documents your application supports, especially when pages contain large images.

For reliability, display separate loading and failure states, retain the page count after successful load, and prevent navigation outside the document’s page range. Treat worker and auxiliary assets as release artifacts: deploy them together with the matching package version. PDFs from external hosts introduce network, CORS, availability, and permission failures that are distinct from parser failures.

pdfjs-dist and React-PDF are open-source packages; this browser integration does not require a screenshot API. Your direct costs come from your application’s hosting, bandwidth for PDFs and support assets, and any external document storage or CDN you choose. Large files and repeated page rendering can increase bandwidth and client resource use even when there is no per-render library fee.

7. Capture a rendered PDF page as an image

If the task is to save a screenshot of a PDF rendered in a browser, keep the PDF rendering and screenshot steps distinct: first load the PDF page in the browser, then capture the resulting page or a selected element. For a reproducible server-side capture, serve the React app and navigate a browser automation tool to its URL; ensure the PDF and worker have loaded before capturing. This is useful for visual previews, but a screenshot is a raster image and does not preserve the PDF’s selectable text, links, or document structure.

Or skip the browser setup

If you need a screenshot of a website that displays a PDF, ScreenshotNeo can return an image or PDF from one GET request. It is a website screenshot API and MCP server from Yorker Media. Its API documentation is at screenshotneo.com/docs.

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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);

Replace the example URL with the page you want to capture. Cookie banners, 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 use its screenshot tools. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, with no card.

FAQ

Can PDF.js display a PDF without React-PDF?

Yes. Use pdfjs-dist directly and manage the loading task, page selection, viewport, canvas, render task, and errors yourself.

Does setting the worker URL enable PDF rendering from a local file?

No. Serve the app over HTTP; the PDF.js worker is not enabled for file:// URLs.

Why is text selectable in one setup but not another?

Canvas pixels alone do not provide a text layer. Use React-PDF’s text layer and stylesheet when selectable text is required.

Should I use the PDF.js viewer inside my app?

The viewer is a UI built on PDF.js display APIs. Mozilla advises using it as a starting point and reskinning or building upon it rather than embedding an unchanged copy.