---
title: "Migrate from react-pdf to Nutrient Web SDK"
canonical_url: "https://www.nutrient.io/guides/web/about/migration-guides/migrating-from-react-pdf/"
md_url: "https://www.nutrient.io/guides/web/about/migration-guides/migrating-from-react-pdf.md"
last_updated: "2026-05-29T15:33:59.514Z"
description: "Migration guide from react-pdf to Nutrient Web SDK — viewer setup, search, annotations, forms, thumbnails, and more with side-by-side code."
---

# Migrating from react-pdf to Nutrient Web SDK

[react-pdf](https://github.com/wojtekmaj/react-pdf) is a popular React wrapper around PDF.js by Wojciech Maj. It provides declarative `<Document>`, `<Page>`, `<Thumbnail>`, and `<Outline>` components for rendering PDFs in React applications.

react-pdf is great for read-only viewing, but it doesn’t support annotation creation, form filling, digital signatures, or real-time collaboration. Nutrient Web SDK provides all of these out of the box with a React-native API.

Want to skip the manual migration? Try [getapdfviewer.com](https://www.getapdfviewer.com) — an agent-assisted quick start that sets up Nutrient in your project automatically.

## Viewer setup

react-pdf requires configuring a web worker, importing CSS files, and composing components. Nutrient replaces all of that with a single `load()` call.

### react-pdf

```jsx

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

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

function PDFViewer() {
  const [numPages, setNumPages] = useState(null);

  return (
    <Document
      file="document.pdf"
      onLoadSuccess={({ numPages }) => setNumPages(numPages)}
    >
      <Page pageNumber={1} />
    </Document>
  );
}

```

You also need to handle the `file` prop memoization carefully — passing `file={{ url: "..." }}` without `useMemo` causes infinite refetches.

### Nutrient

```bash

npm install @nutrient-sdk/viewer

```

```jsx

import NutrientViewer from "@nutrient-sdk/viewer";
import { useEffect, useRef } from "react";

function PDFViewer() {
  const containerRef = useRef(null);

  useEffect(() => {
    NutrientViewer.load({
      container: containerRef.current,
      document: "document.pdf",
    });
    return () => NutrientViewer.unload(containerRef.current);
  }, []);

  return <div ref={containerRef} style={{ height: "100vh" }} />;
}

```

Nutrient needs no worker configuration, no CSS imports, and no memoization pitfalls. The viewer handles text selection, links, and cleanup automatically. The rest of this guide walks through each react-pdf feature and shows the Nutrient equivalent.

**Nutrient guide:** [Getting started](https://www.nutrient.io/sdk/web/getting-started.md) · [Open a document](https://www.nutrient.io/guides/web/open-a-document.md)

<!-- **Deep dive:** [Setting up react-pdf: document and page rendering](/blog/react-pdf-setup-basic-rendering/) · [Loading states, errors, and passwords](/blog/react-pdf-loading-states-errors-passwords/) -->

## Text layer and search

Both libraries render a selectable text layer. react-pdf leaves search to the browser or a custom renderer; Nutrient ships a full-featured search UI and a programmatic API.

### react-pdf

react-pdf renders an invisible text layer for selection and copy-paste, but search is limited to the browser’s `Control-F` or a visual-only `customTextRenderer`:

```jsx

// `searchText` here is component state (e.g. from a controlled input).
<Page
  pageNumber={1}
  customTextRenderer={({ str }) => {
    if (!searchText) return str;
    const regex = new RegExp(`(${searchText})`, "gi");
    return str.replace(
      regex,
      '<mark class="highlight">$1</mark>',
    );
  }}
/>

```

This approach only highlights text visually — it doesn’t provide match counts, navigation between results, or cross-page search. Production code also needs to HTML-escape `str` and regex-escape `searchText` (react-pdf injects the return value as HTML)

<!--  — see the [text layer deep dive][text-blog] for the safe version. -->

### Nutrient

Nutrient includes a full-featured search UI with match counts, result navigation, case sensitivity, and whole-word matching. Programmatic search is also available:

```js

const results = await instance.search("search term");
instance.setSearchState((state) => state.set("results", results));

```

**Nutrient guide:** [Search UI](https://www.nutrient.io/guides/web/user-interface/search.md) · [Text search API](https://www.nutrient.io/guides/web/search.md)

<!-- **Deep dive:** [Text layer and custom text renderer](/blog/react-pdf-text-layer-custom-renderer/) -->

## Annotations

react-pdf displays existing PDF annotations read-only; it has no API to create or edit them. Nutrient ships a full annotation system with a UI and a programmatic API.

### react-pdf

react-pdf renders existing PDF annotations (highlights, links, and popups) read-only via the annotation layer. There’s no API to create, edit, or delete annotations:

```jsx

<Page
  pageNumber={1}
  renderAnnotationLayer={true}
  onGetAnnotationsSuccess={(annotations) => {
    // Read-only access to existing annotations.
    annotations.forEach((a) => console.log(a.subtype));
  }}
/>

```

### Nutrient

Nutrient supports 17+ annotation types with full creation, editing, and export:

```js

const annotation = new NutrientViewer.Annotations.HighlightAnnotation({
  pageIndex: 0,
  rects: [
    new NutrientViewer.Geometry.Rect({
      left: 50,
      top: 100,
      width: 200,
      height: 20,
    }),
  ],
  color: NutrientViewer.Color.YELLOW,
});

await instance.create(annotation);

```

**Nutrient guide:** [Annotation types](https://www.nutrient.io/guides/web/annotations/introduction-to-annotations.md) · [Create annotations](https://www.nutrient.io/guides/web/annotations/create-edit-and-remove/create.md) · [Import/export](https://www.nutrient.io/guides/web/importing-exporting/instant-json.md)

<!-- **Deep dive:** [Annotation layer and forms](/blog/react-pdf-annotation-layer-forms/) -->

## Forms

react-pdf can render interactive form fields but has no API to read or write their values. Nutrient exposes form fields through a direct API and adds validation, calculations, and signatures.

### react-pdf

Interactive forms require `renderForms={true}`. Form fields become standard HTML inputs, but there’s no API for reading or setting values programmatically:

```jsx

<Page
  pageNumber={1}
  renderAnnotationLayer={true}
  renderForms={true}
/>

```

### Nutrient

Forms are interactive by default with full programmatic access:

```js

const formFields = await instance.getFormFields();
await instance.setFormFieldValues({ name: "John Doe" });

```

Nutrient also supports form validation, calculation fields, and digital signatures.

**Nutrient guide:** [Form fields](https://www.nutrient.io/guides/web/forms/introduction-to-forms.md) · [Read form fields](https://www.nutrient.io/guides/web/forms/read-form-fields.md) · [Fill form fields](https://www.nutrient.io/guides/web/forms/form-filling.md)

<!-- **Deep dive:** [Annotation layer and forms](/blog/react-pdf-annotation-layer-forms/) -->

## Thumbnails

Both libraries can render page thumbnails. react-pdf gives you a component to compose into your own sidebar; Nutrient includes a thumbnail sidebar by default.

### react-pdf

react-pdf provides a `Thumbnail` component with click handling:

```jsx

import { Document, Thumbnail } from "react-pdf";

{
  numPages &&
    Array.from({ length: numPages }, (_, i) => (
      <Thumbnail
        key={i + 1}
        pageNumber={i + 1}
        width={150}
        onItemClick={({ pageNumber }) =>
          setCurrentPage(pageNumber)
        }
      />
    ));
}

```

For large documents, you need to manually implement lazy loading and cap `devicePixelRatio` to manage memory.

### Nutrient

Thumbnails are built into the sidebar:

```js

instance.setViewState((v) => v.set("sidebarMode", "THUMBNAILS"));

```

Lazy loading, dots per inch (DPI) management, and scroll syncing are handled automatically.

**Nutrient guide:** [Thumbnail sidebar](https://www.nutrient.io/guides/web/user-interface/sidebar/thumbnail-preview.md) · [View state](https://www.nutrient.io/guides/web/customizing-the-interface/viewstate.md) · [Zooming](https://www.nutrient.io/guides/web/viewer/zooming.md)

<!-- **Deep dive:** [Thumbnails and page navigation](/blog/react-pdf-thumbnails-page-navigation/) -->

## Outline (table of contents)

react-pdf exposes the PDF outline through an `<Outline>` component you wire to page navigation. Nutrient renders the outline in the sidebar with one view state change.

### react-pdf

```jsx

import { Document, Outline } from "react-pdf";

<Document
  file={file}
  onItemClick={({ pageNumber }) => setCurrentPage(pageNumber)}

>

  <Outline
    onLoadSuccess={(outline) => {
      if (!outline) setShowOutline(false);
    }}
  />
</Document>;

```

### Nutrient

```js

instance.setViewState((v) => v.set("sidebarMode", "BOOKMARKS"));

```

**Nutrient guide:** [Document outline](https://www.nutrient.io/guides/web/user-interface/sidebar/document-outline.md) · [Bookmarks](https://www.nutrient.io/guides/web/bookmarks.md)

<!-- **Deep dive:** [Outline (table of contents)](/blog/react-pdf-outline-table-of-contents/) -->

## Loading states and errors

Loading indicators, error states, and password prompts are something you wire up in react-pdf and something Nutrient handles by default.

### react-pdf

react-pdf provides per-component loading/error props:

```jsx

<Document
  file={file}
  loading={<Spinner />}
  error={<ErrorMessage />}
  noData={<EmptyState />}
  onLoadProgress={({ loaded, total }) => {
    setProgress(Math.round((loaded / total) * 100));
  }}
  onPassword={(callback, reason) => {
    const password = prompt("Enter password:");
    callback(password);
  }}

>

  <Page pageNumber={1} loading={<PageSkeleton />} />
</Document>

```

### Nutrient

Nutrient ships built-in loading indicators, error states, and a password dialog. The password prompt appears automatically when the SDK loads an encrypted file — no extra wiring required:

```js

NutrientViewer.load({
  container: containerRef.current,
  document: "encrypted.pdf",
  // No `onPassword` callback needed — the SDK shows its own password dialog.
});

```

If you need to customize the dialog or supply a password programmatically, pass it via the `password` configuration option or override the relevant UI element.

<!-- **Deep dive:** [Loading states, errors, and passwords](/blog/react-pdf-loading-states-errors-passwords/) -->

## Performance

Performance tuning is hands-on in react-pdf, where each optimization is your responsibility. Nutrient applies these automatically.

### react-pdf

Optimizing react-pdf requires several manual steps:

1. **Memoize `file` and `options` props** — Memoization avoids infinite refetch loops.

2. **Cap `devicePixelRatio`** — Capping the ratio reduces memory use on Retina displays.

3. **Virtualize pages** — Render only the visible pages with `react-window`.

4. **Disable unused layers** — Set `renderTextLayer={false}` and `renderAnnotationLayer={false}`.

5. **Use `width` prop not CSS** — CSS resizing causes blurriness.

6. **Enable range requests** — The server must support HTTP 206.

### Nutrient

Most of these are handled automatically — Nutrient manages render prioritization, memory, DPI-aware rendering, and progressive loading without configuration. Range requests still depend on your server returning `Accept-Ranges: bytes`, but Nutrient takes advantage of them automatically when available.

**Nutrient guide:** [Performance best practices](https://www.nutrient.io/guides/web/best-practices/performance.md) · [Streaming](https://www.nutrient.io/guides/web/performance/streaming.md)

<!-- **Deep dive:** [Performance optimization](/blog/react-pdf-performance-optimization/) -->

## Non-Latin fonts and special PDFs

CJK text, older PDFs, and image-heavy documents need extra setup in react-pdf and work without configuration in Nutrient.

### react-pdf

CJK text, older PDFs, and JPEG 2000 images require additional configuration:

```jsx

const options = {
  cMapUrl: `https://unpkg.com/pdfjs-dist@${pdfjs.version}/cmaps/`,
  standardFontDataUrl: `https://unpkg.com/pdfjs-dist@${pdfjs.version}/standard_fonts/`,
  wasmUrl: "/wasm/",
};

<Document file={file} options={options}>
  <Page pageNumber={1} />
</Document>;

```

Production setups should self-host these from `node_modules/pdfjs-dist/` rather than load from a content delivery network (CDN) — CDNs introduce supply-chain risk and may be blocked by strict Content Security Policy (CSP) rules.

### Nutrient

Nutrient handles font rendering, CMap loading, and image decoding internally. No additional configuration is needed.

**Nutrient guide:** [Open a document](https://www.nutrient.io/guides/web/open-a-document.md)

<!-- **Deep dive:** [Non-Latin fonts and special PDFs](/blog/react-pdf-non-latin-fonts-special-pdfs/) -->

<!-- ## Deep-dive tutorials -->

<!-- These tutorials cover react-pdf features in depth: -->

<!-- ### Setup and rendering -->

<!-- - [Setting up react-pdf: document and page rendering](/blog/react-pdf-setup-basic-rendering/) -->

<!-- - [Loading states, errors, and passwords](/blog/react-pdf-loading-states-errors-passwords/) -->

<!-- ### Text and annotations -->

<!-- - [Text layer and custom text renderer](/blog/react-pdf-text-layer-custom-renderer/) -->

<!-- - [Annotation layer and forms](/blog/react-pdf-annotation-layer-forms/) -->

<!-- ### Navigation -->

<!-- - [Outline (table of contents)](/blog/react-pdf-outline-table-of-contents/) -->

<!-- - [Thumbnails and page navigation](/blog/react-pdf-thumbnails-page-navigation/) -->

<!-- ### Advanced -->

<!-- - [Performance optimization](/blog/react-pdf-performance-optimization/) -->

<!-- - [Non-Latin fonts and special PDFs](/blog/react-pdf-non-latin-fonts-special-pdfs/) -->

<!-- - [Custom rendering and hooks](/blog/react-pdf-custom-rendering-hooks/) -->

## Further resources

- [Get started with Nutrient Web SDK](https://www.nutrient.io/sdk/web/getting-started.md)

- [getapdfviewer.com](https://www.getapdfviewer.com) — Agent-assisted quick start

- [How to build a React PDF viewer with react-pdf](https://www.nutrient.io/blog/how-to-build-a-reactjs-pdf-viewer-with-react-pdf.md)

- [Migrate from PDF.js](https://www.nutrient.io/guides/web/about/migration-guides/migrating-from-mozilla-pdfjs.md)

<!-- [text-blog]: /blog/react-pdf-text-layer-custom-renderer/ -->
---

## Related pages

- [Streamline your migration from PDFTron WebViewer](/guides/web/about/migration-guides/migrating-from-pdftron-webviewer.md)
- [Migrating from PDF.js to Nutrient Web SDK](/guides/web/about/migration-guides/migrating-from-mozilla-pdfjs.md)

