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

Mozilla’s PDF.js(opens in a new tab) is an open source JavaScript PDF renderer maintained by Mozilla and used as Firefox’s built-in viewer. It focuses on parsing and rendering PDF documents using the browser’s <canvas> element.

PDF.js is a solid starting point, but building a production-ready PDF experience requires stitching together multiple libraries for annotations, forms, search, and export. Nutrient Web SDK provides all of these out of the box.

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

Setting up a viewer with PDF.js requires configuring a web worker and CMap/font paths, as well as wiring together four separate components.

PDF.js

import { GlobalWorkerOptions, getDocument } from "pdfjs-dist";
// 1. Configure the worker.
GlobalWorkerOptions.workerSrc = new URL(
"pdfjs-dist/legacy/build/pdf.worker.min.mjs",
import.meta.url,
).toString();
// 2. Initialize four components.
const pdfjs = await import("pdfjs-dist/web/pdf_viewer.mjs");
const eventBus = new pdfjs.EventBus();
const linkService = new pdfjs.PDFLinkService({ eventBus });
const findController = new pdfjs.PDFFindController({
linkService,
eventBus,
});
const viewer = new pdfjs.PDFViewer({
container: document.getElementById("pdf-container"),
eventBus,
linkService,
findController,
});
linkService.setViewer(viewer);
// 3. Load the document and wire everything.
const loadingTask = getDocument({
url: "document.pdf",
cMapUrl: `https://unpkg.com/pdfjs-dist@${version}/cmaps/`,
standardFontDataUrl: `https://unpkg.com/pdfjs-dist@${version}/standard_fonts`,
});
const pdfDocument = await loadingTask.promise;
linkService.setDocument(pdfDocument);
findController.setDocument(pdfDocument);
viewer.setDocument(pdfDocument);
// 4. Set initial scale after pages initialize.
eventBus.on("pagesinit", () => {
viewer.currentScaleValue = "auto";
});

You also need to import pdfjs-dist/web/pdf_viewer.css and handle cleanup (destroying the worker and document on unmount).

Nutrient

Terminal window
npm install @nutrient-sdk/viewer
import NutrientViewer from "@nutrient-sdk/viewer";
const instance = await NutrientViewer.load({
container: document.getElementById("pdf-container"),
document: "document.pdf",
});

Nutrient needs no worker configuration, no CMap paths, and no component wiring. The viewer handles rendering, text selection, links, and cleanup automatically. The rest of this guide walks through each PDF.js feature and shows the Nutrient equivalent.

Nutrient guide: Getting started · Open a document

Deep dive: Setting up a custom PDF.js viewer in React · PDF.js EventBus guide

Both libraries support full-document text search. PDF.js exposes it through an event-driven controller, while Nutrient ships with a search UI and a single async API.

PDF.js

Search requires dispatching events on the event bus and listening for results through two separate event handlers:

// Trigger a search.
eventBus.dispatch("find", {
type: "",
query: "search term",
caseSensitive: false,
highlightAll: true,
findPrevious: false,
});
// Listen for results.
eventBus.on("updatefindmatchescount", (evt) => {
console.log(
`${evt.matchesCount.current} of ${evt.matchesCount.total}`,
);
});
eventBus.on("updatefindcontrolstate", (evt) => {
if (evt.state === pdfjs.FindState.NOT_FOUND) {
console.log("No results");
}
});

Nutrient

Nutrient includes a built-in search UI. You can also trigger search programmatically:

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

Nutrient guide: Search UI · Text search API

Annotations

PDF.js leaves annotation tooling to the application — coordinate math, overlay rendering, and persistence all sit on your side. Nutrient ships with a full annotation system, including a UI and persistence model.

PDF.js

PDF.js has no built-in annotation creation API. Building a custom annotation system requires:

  • Coordinate conversion — Translate between the PDF space and screen space using viewport.convertToPdfPoint().
  • React Portals — Inject overlay elements into the PDF.js page DOM.
  • Text selection tracking — Track selection via pointerup events and Range.getClientRects().
  • Third-party libraries — Libraries like annotpdf embed annotations into the PDF binary, and react-rnd provides draggable and resizable elements.
  • Manual rerendering — Rerender on zoom and rotation changes by listening to scalechanging and rotationchanging events.

A text highlight alone involves tracking selection, extracting client rects, converting each to PDF coordinates, storing the annotation, and rendering colored <div> overlays via portals.

PDF.js 3.x+ added a built-in AnnotationEditorLayer for basic free text, ink, stamp, and highlight annotations. But it doesn’t support comments, threads, or database syncing; it’s designed for local markup and export via saveDocument().

Nutrient

// Create a highlight annotation.
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 supports 20+ annotation types out of the box, including highlights, ink, stamps, notes, shapes, and measurement tools — all with a built-in UI, real-time collaboration, and XFDF/Instant syncing.

Nutrient guide: Annotation types · Create annotations · Import/export

Forms

Reading and writing PDF form values takes a different shape in each library. PDF.js requires opting into form mode and iterating page annotations; Nutrient exposes form fields through a direct API.

PDF.js

Interactive forms require setting the annotation mode and manually extracting values:

const viewer = new pdfjs.PDFViewer({
container,
eventBus,
linkService,
annotationMode: pdfjs.AnnotationMode.ENABLE_FORMS,
});
// Reading form values requires iterating all pages.
for (let i = 1; i <= pdfDocument.numPages; i++) {
const page = await pdfDocument.getPage(i);
const annotations = await page.getAnnotations();
for (const annot of annotations) {
if (annot.subtype === "Widget") {
console.log(annot.fieldName, annot.fieldValue);
}
}
}

Nutrient

Forms are interactive by default. Read and set values programmatically:

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

Nutrient guide: Form fields · Read form fields · Fill form fields

Thumbnails and navigation

Thumbnail rendering and page navigation require manual wiring in PDF.js and are built into Nutrient’s sidebar.

PDF.js

PDF.js provides a PDFThumbnailViewer class, but you need to create a container, wire it to the event bus, and sync it with the main viewer:

const thumbnailViewer = new pdfjs.PDFThumbnailViewer({
container: document.getElementById("thumbnail-container"),
eventBus,
linkService,
});
thumbnailViewer.setDocument(pdfDocument);
eventBus.on("pagechanging", (evt) => {
thumbnailViewer.scrollThumbnailIntoView(evt.pageNumber);
});

For custom thumbnails, render pages to small canvases with page.render() and manage lazy loading with IntersectionObserver.

Nutrient

Thumbnails are built into the sidebar and synced automatically:

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

Nutrient guide: Thumbnail sidebar · View state · Zooming

Document outline and metadata

Both libraries can read PDF outline, metadata, and attachments. PDF.js exposes them as raw data you render yourself; Nutrient renders them in the sidebar by default.

PDF.js

const outline = await pdfDocument.getOutline();
const metadata = await pdfDocument.getMetadata();
const attachments = await pdfDocument.getAttachments();

You then need to build your own sidebar UI, handle outline navigation via linkService.goToDestination(), and manage file downloads for attachments.

Nutrient

The outline sidebar is built in:

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

Nutrient guide: Document outline · Bookmarks

Performance and loading

Performance tuning is hands-on in PDF.js, where you configure workers, range requests, and render cancellation yourself. Nutrient handles these automatically.

PDF.js

Optimizing PDF.js requires manual configuration:

  • Worker setup — Configure GlobalWorkerOptions.workerSrc yourself.
  • Range requests — Set rangeChunkSize and disableAutoFetch to control fetching.
  • Render cancellation — Track active RenderTask instances and call .cancel() on fast scroll.
  • DPI management — Account for window.devicePixelRatio in canvas operations.
  • Streaming — Use PDFDataRangeTransport for custom data sources.

Nutrient

Performance optimizations are handled automatically, including progressive loading, render prioritization, and memory management. No configuration is needed for standard use cases.

Nutrient guide: Performance best practices · Streaming · Accessibility

Server-side processing

Server-side PDF workflows in PDF.js require a different library for each task. Nutrient consolidates them into a single API.

PDF.js

Server-side workflows require stitching together multiple tools:

TaskLibrary
Text extractionpdfjs-dist in Node.js
Page manipulationpdf-lib
Thumbnail generationpdf2pic (requires GraphicsMagick)
Office → PDFLibreOffice (headless)
eBook → PDFCalibre
HTML → PDFPuppeteer
OCRocrmypdf

Nutrient

Nutrient Web SDK and Document Engine handle server-side processing through a unified API — text extraction, page manipulation, format conversion, optical character recognition (OCR), and rendering — without requiring system-level dependencies.

Nutrient guide: Text extraction · OCR · Conversion · Page manipulation · Save/export

Further resources