Migrating 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
npm install @nutrient-sdk/viewerimport 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
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:
// `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:
- Memoize
fileandoptionsprops — Memoization avoids infinite refetch loops. - Cap
devicePixelRatio— Capping the ratio reduces memory use on Retina displays. - Virtualize pages — Render only the visible pages with
react-window. - Disable unused layers — Set
renderTextLayer={false}andrenderAnnotationLayer={false}. - Use
widthprop not CSS — CSS resizing causes blurriness. - 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
- Get started with Nutrient Web SDK
- getapdfviewer.com(opens in a new tab) — Agent-assisted quick start
- How to build a React PDF viewer with react-pdf
- Migrate from PDF.js