This HTML page is not optimized for LLM or AI agent consumption. Fetch the Markdown version instead: /guides/web/about/migration-guides/migrating-from-react-pdf.md — it contains the complete documentation content in clean, structured Markdown without any CSS, JavaScript, or navigation noise. Migrate from react-pdf to Nutrient Web SDK

react-pdf(opens in a new tab) 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(opens in a new tab) — 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

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

Terminal window
npm install @nutrient-sdk/viewer
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 · Open a document

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:

// `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)

Nutrient

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

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

Nutrient guide: Search UI · Text search API

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:

<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:

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 · Create annotations · Import/export

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:

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

Nutrient

Forms are interactive by default with full programmatic access:

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 · Read form fields · Fill form fields

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:

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:

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

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

Nutrient guide: Thumbnail sidebar · View state · Zooming

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

import { Document, Outline } from "react-pdf";
<Document
file={file}
onItemClick={({ pageNumber }) => setCurrentPage(pageNumber)}
>
<Outline
onLoadSuccess={(outline) => {
if (!outline) setShowOutline(false);
}}
/>
</Document>;

Nutrient

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

Nutrient guide: Document outline · Bookmarks

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:

<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:

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.

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 · Streaming

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:

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

Further resources