---
title: "Migrate from PDF.js to Nutrient Web SDK"
canonical_url: "https://www.nutrient.io/guides/web/about/migration-guides/migrating-from-mozilla-pdfjs/"
md_url: "https://www.nutrient.io/guides/web/about/migration-guides/migrating-from-mozilla-pdfjs.md"
last_updated: "2026-06-11T00:00:00.000Z"
description: "Step-by-step migration guide from Mozilla PDF.js to Nutrient Web SDK. Covers viewer setup, search, annotations, forms, thumbnails, and more with side-by-side code comparisons."
---

# Migrating from PDF.js to Nutrient Web SDK

[Mozilla’s PDF.js](https://en.wikipedia.org/wiki/PDF.js) 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](https://www.getapdfviewer.com) — 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

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

```bash

npm install @nutrient-sdk/viewer

```

```js

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](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 a custom PDF.js viewer in React](https://www.nutrient.io/blog/pdfjs-react-viewer-setup.md) · [PDF.js EventBus guide](https://www.nutrient.io/blog/pdfjs-eventbus-guide.md)

## Text search

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:

```js

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

```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 search with PDFFindController](https://www.nutrient.io/blog/pdfjs-text-search-pdffindcontroller.md)

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

```js

// 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](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:** [Text highlight annotations](https://www.nutrient.io/blog/pdfjs-text-highlight-annotations.md) · [Area annotations with canvas capture](https://www.nutrient.io/blog/pdfjs-area-annotations-canvas-capture.md) · [Sticky note annotations](https://www.nutrient.io/blog/pdfjs-sticky-note-annotations.md) · [Built-in `AnnotationEditorLayer`](https://www.nutrient.io/blog/pdfjs-annotation-editor-layer/) · [Exporting PDFs with embedded annotations](https://www.nutrient.io/blog/pdfjs-exporting-pdfs-embedded-annotations/)

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

```js

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:

```js

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

```

**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:** [Native AnnotationLayer and forms](https://www.nutrient.io/blog/pdfjs-native-annotation-layer-forms.md)

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

```js

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:

```js

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

```

**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:** [Thumbnail sidebar with PDFThumbnailViewer](https://www.nutrient.io/blog/pdfjs-thumbnail-sidebar.md) · [Page navigation, zoom, and rotation](https://www.nutrient.io/blog/pdfjs-navigation-zoom-rotation.md)

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

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

```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:** [Document outline, bookmarks, and metadata](https://www.nutrient.io/blog/pdfjs-document-outline-bookmarks-metadata/) -->

## 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](https://www.nutrient.io/guides/web/best-practices/performance.md) · [Streaming](https://www.nutrient.io/guides/web/performance/streaming.md) · [Accessibility](https://www.nutrient.io/guides/web/viewer/accessibility.md)

<!-- **Deep dive:** [Advanced loading, streaming, and workers](https://www.nutrient.io/blog/pdfjs-advanced-loading-streaming-workers/) · [Accessibility, StructTree, and printing](https://www.nutrient.io/blog/pdfjs-accessibility-structtree-printing/) -->

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

| Task                 | Library                             |
| -------------------- | ----------------------------------- |
| Text extraction      | `pdfjs-dist` in Node.js             |
| Page manipulation    | `pdf-lib`                           |
| Thumbnail generation | `pdf2pic` (requires GraphicsMagick) |
| Office → PDF         | LibreOffice (headless)              |
| eBook → PDF          | Calibre                             |
| HTML → PDF           | Puppeteer                           |
| OCR                  | `ocrmypdf`                          |

### 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](https://www.nutrient.io/guides/web/features/text-extraction.md) · [OCR](https://www.nutrient.io/guides/web/ocr.md) · [Conversion](https://www.nutrient.io/guides/web/conversion.md) · [Page manipulation](https://www.nutrient.io/guides/web/editor/add-page.md) · [Save/export](https://www.nutrient.io/guides/web/save-a-document/to-arraybuffer.md)

**Deep dive:** [Server-side text extraction](https://www.nutrient.io/blog/pdfjs-server-side-text-extraction.md) · [PDF page manipulation with pdf-lib](https://www.nutrient.io/blog/pdfjs-pdf-page-manipulation-pdf-lib.md)

<!-- · [Generating thumbnails with pdf2pic](https://www.nutrient.io/blog/pdfjs-generating-pdf-thumbnails-pdf2pic/) · [File format conversion to PDF](https://www.nutrient.io/blog/pdfjs-file-format-conversion-to-pdf/) -->

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

<!-- These tutorials cover PDF.js features in depth, showing the complexity involved — and how Nutrient simplifies each one: -->

<!-- ### Viewer setup and core concepts -->

<!-- - [Setting up a custom PDF.js viewer in React](https://www.nutrient.io/blog/pdfjs-react-viewer-setup.md) -->

<!-- - [PDF.js EventBus guide](https://www.nutrient.io/blog/pdfjs-eventbus-guide.md) -->

<!-- - [Page navigation, zoom, and rotation](https://www.nutrient.io/blog/pdfjs-navigation-zoom-rotation.md) -->

<!-- ### Search -->

<!-- - [Text search with PDFFindController](https://www.nutrient.io/blog/pdfjs-text-search-pdffindcontroller.md) -->

<!-- ### Coordinate systems and overlays -->

<!-- - [Coordinate systems: PDF to screen](https://www.nutrient.io/blog/pdfjs-coordinate-systems-pdf-to-screen.md) -->

<!-- - [Rendering overlays with React Portals](https://www.nutrient.io/blog/pdfjs-rendering-overlays-react-portals.md) -->

<!-- ### Custom annotations -->

<!-- - [Text highlight annotations](https://www.nutrient.io/blog/pdfjs-text-highlight-annotations.md) -->

<!-- - [Area annotations with canvas capture](https://www.nutrient.io/blog/pdfjs-area-annotations-canvas-capture.md) -->

<!-- - [Sticky note annotations](https://www.nutrient.io/blog/pdfjs-sticky-note-annotations.md) -->

<!-- - [Exporting PDFs with embedded annotations](https://www.nutrient.io/blog/pdfjs-exporting-pdfs-embedded-annotations/) -->

<!-- ### Native PDF.js features -->

<!-- - [Built-in AnnotationEditorLayer](https://www.nutrient.io/blog/pdfjs-annotation-editor-layer.md) -->

<!-- - [Native AnnotationLayer and forms](https://www.nutrient.io/blog/pdfjs-native-annotation-layer-forms.md) -->

<!-- - [Document outline, bookmarks, and metadata](https://www.nutrient.io/blog/pdfjs-document-outline-bookmarks-metadata/) -->

<!-- - [Thumbnail sidebar](https://www.nutrient.io/blog/pdfjs-thumbnail-sidebar.md) -->

<!-- - [Accessibility, StructTree, and printing](https://www.nutrient.io/blog/pdfjs-accessibility-structtree-printing/) -->

<!-- - [Advanced loading, streaming, and workers](https://www.nutrient.io/blog/pdfjs-advanced-loading-streaming-workers/) -->

<!-- ### Server-side processing -->

<!-- - [Server-side text extraction](https://www.nutrient.io/blog/pdfjs-server-side-text-extraction.md) -->

<!-- - [PDF page manipulation with pdf-lib](https://www.nutrient.io/blog/pdfjs-pdf-page-manipulation-pdf-lib.md) -->

<!-- - [Generating thumbnails with pdf2pic](https://www.nutrient.io/blog/pdfjs-generating-pdf-thumbnails-pdf2pic/) -->

<!-- - [File format conversion to PDF](https://www.nutrient.io/blog/pdfjs-file-format-conversion-to-pdf/) -->

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

- [Build a React PDF viewer with pdfjs-dist and Next.js](https://www.nutrient.io/blog/how-to-build-a-reactjs-viewer-with-pdfjs.md)

- [JavaScript PDF viewer tutorial with PDF.js](https://www.nutrient.io/blog/how-to-build-a-javascript-pdf-viewer-with-pdfjs.md)

- [PDF.js vs. Nutrient](https://www.nutrient.io/blog/pdfjs-vs-nutrient/)

- [Complete guide to PDF.js](https://www.nutrient.io/blog/complete-guide-to-pdfjs.md)

- [Migrate from react-pdf](https://www.nutrient.io/guides/web/about/migration-guides/migrating-from-react-pdf.md)
---

## Related pages

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

