Skip to content

Upgrade guide from version 10.x to 11.x

Wojciech Maj edited this page Sep 10, 2026 · 5 revisions

React-PDF 11 updates PDF.js from 5.4.296 to 6.3.289 and enables React Suspense and Error Boundaries by default. For the previous API, see the React-PDF 10.x documentation.

React 19 is required

Upgrade react and react-dom to version 19 or later, along with @types/react and @types/react-dom if you use TypeScript. Support for React 16.8, 17, and 18 has been dropped.

Preact users need a preact/compat version with React's use API.

Suspense and Error Boundaries are enabled by default

Document, Page, Thumbnail, and Outline now suspend while loading and send failures to the nearest Error Boundary. Move your loading and error UI into boundary fallbacks:

import { Suspense } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import { Document, Page } from 'react-pdf';

<ErrorBoundary fallback={<p>Could not load PDF.</p>}>
  <Suspense fallback={<p>Loading PDF…</p>}>
    <Document file={file}>
      <Page pageNumber={1} />
    </Document>
  </Suspense>
</ErrorBoundary>;

Install react-error-boundary for this example, or use your application's existing Error Boundary. No React-PDF provider is needed.

Suspense waits for document, page, or outline data. Canvas, text, annotations, and the structure tree still render progressively; their failures also reach the Error Boundary. noData continues to handle empty input.

Keep the previous loading and error behavior

Set suspense={false} to continue using the loading and error props:

<Document error="Could not load PDF." file={file} loading="Loading PDF…" suspense={false}>
  <Page pageNumber={1} />
</Document>

Children inherit the document's setting and can override it. Set suspense={false} explicitly on standalone Page, Thumbnail, or Outline components if needed.

Callbacks and retries

Load callbacks remain available, but success callbacks run after the consuming component commits. Keep password and progress UI outside the suspended viewer.

Suspense compares plain file/options objects by value. Keep binary inputs, workers, and range transports outside the suspended subtree. Concurrent initial loads may share password/progress handlers, and superseded loads may still call them. Do not rely on unmounting a viewer to immediately stop these callbacks.

Use startTransition when changing the file or page to keep previously revealed content visible while the new data loads:

import { startTransition } from 'react';

function changePage(pageNumber: number) {
  startTransition(() => {
    setPageNumber(pageNumber);
  });
}

Here, setPageNumber updates state in a component outside the suspended viewer. Suspense waits for page data; it does not wait for the canvas to finish painting.

Place the Error Boundary around Document so resetting it retries the entire load:

<ErrorBoundary
  fallbackRender={({ resetErrorBoundary }) => (
    <button onClick={resetErrorBoundary}>Retry</button>
  )}
>
  <Suspense fallback={<p>Loading PDF…</p>}>
    <Document file={file}>
      <Page pageNumber={1} />
    </Document>
  </Suspense>
</ErrorBoundary>;

To reload a mounted document explicitly, change its React key.

Dropped support for older browsers and Node.js versions

React-PDF supports the latest versions of all major modern browsers.

Minimum browser requirements are Chrome 125 and Safari 18 (iOS 18). Versions below the latest releases, but meeting these minimums, may require additional polyfills, bundler transpilation, and the legacy PDF.js worker.

React-PDF requires Node.js 22.13.0 or newer.

See the browser compatibility changes and PDF.js package requirements.

Update copied PDF.js files

If you copy the PDF.js worker, cMaps, standard fonts, or wasm files into your application, copy them again from the updated pdfjs-dist package. The worker version must match the PDF.js version used by React-PDF.

If you configure a worker through a CDN, use pdfjs.version instead of a hardcoded version:

import { pdfjs } from 'react-pdf';

pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`;

Set this in the same module where you use <Document> and <Page>. If you also depend on pdfjs-dist directly, keep its version aligned with React-PDF's dependency.

Direct calls to pdfjs.getDocument require an options object

If you call pdfjs.getDocument yourself, wrap URLs in { url } and binary data in { data }:

- const loadingTask = pdfjs.getDocument('/example.pdf');
+ const loadingTask = pdfjs.getDocument({ url: '/example.pdf' });
- const loadingTask = pdfjs.getDocument(arrayBuffer);
+ const loadingTask = pdfjs.getDocument({ data: arrayBuffer });

No action is required for React-PDF's file prop. For example, <Document file="/example.pdf" /> still works.

PDFDocumentProxy.destroy() was removed

If you load and manage PDF.js documents yourself, destroy the loading task instead:

- await pdf.destroy();
+ await pdf.loadingTask.destroy();

You can also retain the task returned by pdfjs.getDocument and call loadingTask.destroy() directly. React-PDF handles cleanup for documents it loads, so you do not need to add this to onLoadSuccess or your component's cleanup.

See the PDF.js API removal.

Several PDF.js APIs now return Map or Set

If you use document or page proxies received through React-PDF callbacks, update any code that reads these results:

API New result
pdf.getDestinations() Map
pdf.getAttachments() Map or null
pdf.getViewerPreferences() Map or null
pdf.getOpenAction() Map or null
pdf.getJSActions() / page.getJSActions() Map or null
pdf.getFieldObjects() Map or null
pdf.getMarkInfo() Map or null
pdf.getPermissions() Set or null

Use .get() for map lookups and .has() to check permissions:

  const destinations = await pdf.getDestinations();
- const destination = destinations['Chapter1'];
+ const destination = destinations.get('Chapter1');
  const permissions = await pdf.getPermissions();
- const canPrint = permissions?.includes(pdfjs.PermissionFlag.PRINT);
+ const canPrint = permissions?.has(pdfjs.PermissionFlag.PRINT);

For iteration, replace Object.entries(result) with result.entries(). Custom document information returned as info.Custom by pdf.getMetadata() is also a Map when present; the outer info object is unchanged.

In pdfjs-dist 6.3.289, the TypeScript declaration for getMarkInfo() still describes an object, although the runtime returns a Map. TypeScript users accessing these flags should account for this upstream declaration mismatch.

See the 6.1, 6.2, and 6.3 release notes for these API changes.

Custom PDF.js data factories were consolidated

If you pass CMapReaderFactory, StandardFontDataFactory, or WasmFactory through <Document options={...}> or directly to pdfjs.getDocument, replace those custom implementations with a single BinaryDataFactory. Its fetch({ kind, filename }) method handles the different resource types. This requires adapting your factory implementation, not just renaming an option.

The cMapUrl, standardFontDataUrl, and wasmUrl options remain available. No factory migration is needed if you only configure these URLs. As before, custom main-thread fetching requires useWorkerFetch: false.

The length option was also removed from pdfjs.getDocument options; remove it if you supplied it there. The length constructor argument of PDFDataRangeTransport is separate and remains supported.

See the factory changes and removal of the loading option.

Annotation rendering callbacks wait for rendering to finish

onRenderAnnotationLayerSuccess now runs after PDF.js's asynchronous annotation rendering completes. Rendering failures are reported through onRenderAnnotationLayerError. If you access rendered annotation elements, do so in the success callback instead of assuming they are available immediately after rendering <Page>.

Clone this wiki locally