On March 25, 2026, phuocng archived the react-pdf-viewer GitHub repo. I thought it was a typo when I first saw the "Archived" label. For years, that library had been popular with React developers for projects that needed a real toolbar and search shipped in the box but couldn't justify an enterprise SDK. Now it's read-only, frozen at v3.12.0 from March 2023.
A colleague of mine wrote about the ways to build a React PDF viewer back in 2024. That guide predates both the archival and this year's AI scaffolding tools, so I rebuilt the comparison from scratch.
Over the next two weeks I built the same React PDF viewer four different ways: with PDF.js scaffolded by Codex, dropped in with wojtekmaj/react-pdf, with @anaralabs/lector, and assembled with React PDF Kit (which I help build). The fastest path was live in a single sitting. The slowest took real, sustained work, and it still didn't ship search across pages.
The names in this space are a maze. The libraries called react-pdf, @react-pdf/renderer, and React PDF Kit are three different things. I'll keep them straight as I go.
A few options I'm leaving out on purpose. Phuocng's react-pdf-viewer is in the intro because it's the news, not in the lineup because it's no longer a choice. If you're already running it in production and weighing what to switch to, there's a side-by-side comparison at react-pdf-kit.dev/react-pdf-viewer-alternative.
Two more get their own pieces later in this series: EmbedPDF, which takes a different architectural path (PDFium via WebAssembly instead of PDF.js), and the Next.js-specific issues (App Router, Turbopack, hydration, worker resolution) that are deep enough to stand on their own.
The 4 PDF viewer options at a glance
Before any code, here's the highest-level view.
| Option | One-line role | License |
|---|---|---|
| PDF.js (with or without Codex) | The foundation everything else is built on. You assemble the viewer. | Apache 2.0 |
| wojtekmaj/react-pdf | Drop-in <Document> and <Page> rendering primitive. No toolbar, no search UI. |
MIT |
| @anaralabs/lector | Headless composable primitives. Bring your own UI. | MIT |
| React PDF Kit | Full React viewer with toolbar, search, bbox highlighting, form filling, mobile responsive. | Commercial (perpetual) |
Which one fits depends on what you're building. A static report viewer in an internal tool has different requirements than a contract review app with citation highlighting on AI-extracted regions. That's the lens for the comparison at the end.
How we'll evaluate them
I'll judge each option on seven things that matter when you're actually building this:
- Feature completeness. What you get on day one (toolbar, search, bbox highlighting, navigation, print/download).
- Maintenance posture. Recent commits, release cadence, npm download trend.
- React compat and API style. Components, hooks, headless primitives. Does the API match how your team writes React?
- Licensing tolerance. MIT vs. Apache vs. commercial, and whether your project can accept a paid library.
-
Time-to-ship. How long from
npm installto a viewer your PM would accept. - Documentation. React-specific and current with worked examples, or piecemeal from a JavaScript API reference and GitHub issues.
- Support model. Community (GitHub issues, Discussions, Stack Overflow) or commercial (paid channel, dedicated point of contact). Different teams need different things.
All four options work with Vite, Webpack, and the standard React build tools. Build tooling isn't the constraint anymore.
Versions tested for this comparison: React 19, PDF.js 5, react-pdf 10.4.1, @anaralabs/lector 3.13.3, @react-pdf-kit/viewer 2.7.2, on Chrome 13x and Safari 18x / macOS. All current as of June 2026.
Building it yourself with PDF.js and Codex
PDF.js is Mozilla's open-source PDF renderer. It's what the official Firefox viewer uses, and it's the rendering engine under every other React PDF library on this list.
Apache 2.0 license, free for commercial use. Around 17M weekly downloads, the broadest community of any PDF tool in JavaScript.
The typical "I'll build my own" workflow in 2026 looks different from what it did two years ago. You open Codex in the project directory, prompt the agent to scaffold a React component that loads a PDF and renders pages with pdfjs-dist, watch the agent create files and run the dev server, then start fixing what the demo skipped. Pure hand-written DIY is the exception now, not the rule. GitHub Copilot fills in lines as you go. Browser-based scaffolders like v0, Bolt, and Lovable offer a similar shape from a different surface.
Whatever wrote the code, you end up owning the same pieces: worker configuration (the pdfjs.GlobalWorkerOptions.workerSrc dance), the getDocument plus render loop on a canvas, pagination state, navigation chrome (prev/next, page indicator, zoom), and a toolbar if you want one.
On June 26, 2026, I had Codex (GPT-5.5) scaffold a PDF.js viewer in a fresh React 19 project. The prompt was: "Scaffold a React component that loads a PDF from /sample.pdf and renders the first page with pdfjs-dist." Codex generated a minimal component that configured the PDF.js worker, loaded /sample.pdf, fetched page 1, created a viewport at 1.5x scale, resized a canvas, and rendered the page onto it.
PDF.js React 19 scaffold
import { useEffect, useRef } from 'react';
import * as pdfjs from 'pdfjs-dist';
const workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs',
import.meta.url
).toString();
pdfjs.GlobalWorkerOptions.workerSrc = workerSrc;
export default function PdfViewer() {
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
let cancelled = false;
async function renderFirstPage() {
const pdf = await pdfjs.getDocument('/sample.pdf').promise;
const page = await pdf.getPage(1);
const viewport = page.getViewport({ scale: 1.5 });
const canvas = canvasRef.current;
if (!canvas || cancelled) return;
canvas.width = viewport.width;
canvas.height = viewport.height;
await page.render({ canvas, viewport }).promise;
await pdf.destroy();
}
renderFirstPage();
return () => {
cancelled = true;
};
}, []);
return <canvas ref={canvasRef} />;
}
Code sample: PDF.js React 19 scaffold
That code renders page one of one PDF onto a canvas. It's the demo version of a PDF viewer. (The page.render({ canvas, viewport }) signature Codex generated here needs pdfjs-dist 5.x; on 4.x you pass canvasContext instead.) For the deeper layer mechanics, the Understanding PDF.js Layers and How to Use them in React.js post covers what's happening under the hood.
For the second pass, I asked Codex for something closer to a real viewer rather than a one-page render demo. The prompt was:
"Create a React component using pdfjs-dist to load /sample.pdf and render pages on a canvas. Add previous/next page navigation with a page counter, zoom in/out controls, search, a text layer for copy-paste, loading state, status messages, and error handling."
This produced a more complete demo with navigation, zoom, page search, a text layer, and basic loading/error states. It was still not production-ready: no virtual scrolling, no mobile gestures, and only limited search/text-layer behavior.
Code sample: PDF.js React 19 with AI assistance
Five things trip up a lot of the PDF.js-on-React projects I've reviewed, and Codex-scaffolded code isn't any more immune to them than hand-written code.
1. Virtual scrolling and lazy page loading. Render every page on load and your 200-page PDF eats memory until the tab dies. Codex's first answer almost never wires up an IntersectionObserver or a windowed render queue.
2. Mobile pinch-to-zoom and swipe gestures. Touch event handling for pinch is fiddly. Most AI-scaffolded code uses CSS transforms that look right on desktop and break the canvas resolution on retina mobile.
3. Search across the whole document, not just the visible page. PDF.js exposes getTextContent per page, but assembling a cross-document search index, highlighting matches, and scrolling to them takes real code.
4. Print fidelity across browsers. Chrome, Safari, and Firefox each render the print preview from a canvas-based PDF viewer differently. Vector print output requires going back to the original PDF, not the rasterized canvas. This is the failure mode that surprises people the most.
5. Text selection and copy-paste across pages. The default scaffold renders a canvas without a text layer. The text on the screen isn't selectable. AI scaffolds almost universally skip getTextContent and the text-layer overlay, which is typically one of the first things users ask for.
Codex can build a production PDF viewer in React. The open question is how many iteration cycles it takes before the edge cases stop firing, and that's usually far more than the scaffold suggests. Whether Codex writes it or you do, you own these challenges.
Docs and support match the DIY shape. Mozilla's PDF.js API reference is extensive but JSDoc-style and written for general JavaScript, not React. Examples need adapting, and real-world patterns sometimes come from the source or GitHub issues alongside the docs. Support is community-only: issues on mozilla/pdf.js, Stack Overflow, and whatever you've collected in your own bookmarks.
This is why the React-specific libraries below exist. Each one wraps PDF.js so you can skip the worker-config-and-rendering-loop work and focus on your product.
Drop-in rendering with wojtekmaj/react-pdf
wojtekmaj/react-pdf owns the react-pdf npm package name. It's a PDF.js wrapper providing <Document> and <Page> components, originally by Jason Quense and maintained by Wojciech Maj.
MIT licensed. Around 4.8M weekly downloads. Latest 10.4.1 published Feb 25, 2026. v10 was a major release: PDF.js 5.x upgrade, ESM-only, dropped CommonJS.
The disambiguation is worth getting out of the way before anything else, because three different libraries get called variations of "react-pdf" and it matters which one you mean. The npm package called react-pdf is a PDF viewer wrapper by Wojciech Maj, the one in this section. Not to be confused with @react-pdf/renderer by Diego Muracciole, which generates PDFs from React components and isn't a viewer at all. And not to be confused with React PDF Kit, which is a different viewer with a different architecture, covered later in this article.
import { Document, Page, pdfjs } from 'react-pdf'
import 'react-pdf/dist/Page/AnnotationLayer.css'
import 'react-pdf/dist/Page/TextLayer.css'
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs',
import.meta.url,
).toString()
export function PdfViewer() {
return (
<Document file="/sample.pdf">
<Page pageNumber={1} />
</Document>
)
}
That renders page one with the text and annotation layers. Notice how little viewer behavior exists around the page: no page counter, no previous/next button, no zoom control, no search box. You get the rendering primitive first.
The next step is usually to build viewer chrome around those primitives: the buttons, counters, zoom inputs, and sidebars that wrap the document. Here's the same idea with previous/next navigation and a page counter:
import { useState } from "react";
import { Document, Page, pdfjs } from "react-pdf";
import "react-pdf/dist/Page/AnnotationLayer.css";
import "react-pdf/dist/Page/TextLayer.css";
const SAMPLE_PDF_URL =
"https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf";
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
"pdfjs-dist/build/pdf.worker.min.mjs",
import.meta.url,
).toString();
export function WojtekmajReactPdf() {
const [numPages, setNumPages] = useState(0);
const [pageNumber, setPageNumber] = useState(1);
const canGoBack = pageNumber > 1;
const canGoForward = numPages > 0 && pageNumber < numPages;
function handleLoadSuccess({ numPages }: { numPages: number }) {
setNumPages(numPages);
setPageNumber(1);
}
function goToPreviousPage() {
setPageNumber((currentPage) => Math.max(currentPage - 1, 1));
}
function goToNextPage() {
setPageNumber((currentPage) => Math.min(currentPage + 1, numPages));
}
return (
<section aria-labelledby="react-pdf-title">
<div>
<div
style={{
display: "flex",
gap: 8,
alignItems: "center",
justifyContent: "center",
padding: 10,
borderBottom: "1px solid var(--border)",
background: "var(--social-bg)",
}}
aria-label="PDF page navigation"
>
<button
type="button"
onClick={goToPreviousPage}
disabled={!canGoBack}
style={{
...(!canGoBack ? { cursor: "not-allowed", opacity: 0.45 } : {}),
}}
>
Previous
</button>
<span aria-live="polite">
Page {pageNumber} {numPages ? `of ${numPages}` : ""}
</span>
<button
type="button"
onClick={goToNextPage}
disabled={!canGoForward}
style={{
...(!canGoForward
? { cursor: "not-allowed", opacity: 0.45 }
: {}),
}}
>
Next
</button>
</div>
</div>
<div style={{ width: "max-content", margin: "0 auto" }}>
<Document file={SAMPLE_PDF_URL} onLoadSuccess={handleLoadSuccess}>
<div>
<Page pageNumber={pageNumber} width={680} />
</div>
</Document>
</div>
</section>
);
}
Code sample: wojtekmaj/react-pdf with page navigation
That's the shape most teams hit quickly: Document reports the page count, your state tracks the current page, your buttons gate navigation. The library also ships <Outline> and <Thumbnail> primitives, and for plenty of projects that's all you need.
What does not ship as a finished product feature: A built-in toolbar, search UI, full zoom toolbar, bbox highlighting, or a form filling UI. The README is honest about this: it is a renderer.
If you need a toolbar, you build it. If you need search, you wire it on top of PDF text extraction. If you need annotations or coordinate overlays, you implement them yourself. This is the wall I see most React PDF projects hit. The library does its job. The job is just narrower than most product specs assume.
Docs are at projects.wojtekmaj.pl/react-pdf, though they're minimal. For anything past the basics you end up in the GitHub README or PDF.js's Mozilla API docs. Support is community: GitHub Issues and Discussions.
When it fits: Basic read-only viewing, embedded PDFs inside an app that already has its own controls, or a custom viewer where you are happy to build page navigation, zoom, search, and other chrome yourself.
When it doesn't: You need a complete toolbar, search UI, bbox highlighting, form filling UI, or mobile viewer experience shipped without writing those pieces.
Note: If you want even less than wojtekmaj/react-pdf ships, @mikecousins/react-pdf provides a single usePdf hook that renders one page onto one canvas (8.1.0 published May 22, 2026, MIT, around 6.3K weekly downloads, hook architecture stable since around 2019). It's the minimal end of the React PDF library spectrum, a small dependency for a small job.
Headless composable primitives with @anaralabs/lector
@anaralabs/lector is the newest entrant in this lineup and the most interesting structurally. It's a headless PDF viewer toolkit for React from Anara, MIT licensed.
Around 25K weekly downloads and 60+ versions since March 2025. Latest 3.13.4 shipped June 30, 2026. I tested against 3.13.3 from June 22.
The architecture is composable primitives, the same shape Radix UI brought to component libraries:
import { CanvasLayer, Page, Pages, Root, TextLayer } from "@anaralabs/lector";
import "pdfjs-dist/legacy/build/pdf.worker.min.mjs";
import "pdfjs-dist/web/pdf_viewer.css";
export function AnaralabsLectorInitial() {
return (
<Root
source="/sample.pdf"
style={{
width: "100%",
height: "500px",
border: "1px solid #e5e4e7",
overflow: "hidden",
borderRadius: "10px",
}}
colorScheme="light"
zoom={1.0001}
>
<Pages className="p-4">
<Page>
<CanvasLayer />
<TextLayer />
</Page>
</Pages>
</Root>
);
}
That code says more about the architecture than the UI. <Root> owns the document context. <Pages> decides where pages render. <Page> describes each page. <CanvasLayer> paints the PDF page. <TextLayer> puts selectable text on top. You are not dropping in a finished toolbar; you are composing the viewer surface from parts.
The next practical step is to compose a small control surface. This example adds zoom out, current zoom, and zoom in controls above the pages:
import {
CanvasLayer,
CurrentZoom,
Page,
Pages,
Root,
TextLayer,
ZoomIn,
ZoomOut,
} from "@anaralabs/lector";
import "pdfjs-dist/legacy/build/pdf.worker.min.mjs";
import "pdfjs-dist/web/pdf_viewer.css";
const SAMPLE_PDF_URL =
"https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf";
export function AnaralabsLector() {
return (
<Root
source={SAMPLE_PDF_URL}
zoom={1.0001}
zoomOptions={{ minZoom: 0.5, maxZoom: 3 }}
colorScheme="light"
>
<div
style={{
display: "flex",
gap: 8,
alignItems: "center",
justifyContent: "center",
paddingTop: 10,
}}
aria-label="PDF zoom controls"
>
<ZoomOut type="button" aria-label="Zoom out">
-
</ZoomOut>
<CurrentZoom aria-label="Current zoom" readOnly />
<ZoomIn type="button" aria-label="Zoom in">
+
</ZoomIn>
</div>
<Pages>
<Page>
<CanvasLayer />
<TextLayer />
</Page>
</Pages>
</Root>
);
}
Code sample: @anaralabs/lector with zoom controls
The difference from the minimal example is the control composition. ZoomOut, CurrentZoom, and ZoomIn are viewer primitives, just like CanvasLayer and TextLayer are page-layer primitives. You place them where your product needs them and style the surrounding UI yourself. In the local example above, the viewer renders the PDF canvas, keeps selectable text through the text layer, and adds a small zoom control row.
Per the README, lector has primitives and support for search, page thumbnails, outline navigation, dark mode, pan and zoom, form filling support, internal and external link handling, and mobile-friendly viewing. The important word is still headless. Those capabilities do not arrive as one finished opinionated viewer shell. You decide what to expose, where to place it, and how it looks in your app.
The Anara context matters here. Anara also maintains forks of PDF.js and vercel/ai. They're building AI tools that consume PDFs, and lector is the viewer half of that. If you're building a chat-with-PDF or document intelligence product and you want a viewer you can fully restyle to match your app, that positioning is the honest reason lector exists.
Docs are at lector-weld.vercel.app with worked examples per primitive. Support is community: GitHub Issues against anaralabs/lector.
When it fits: You want full control over the viewer UI but do not want to write the PDF.js plumbing. You are comfortable assembling layers and controls from primitives so the viewer matches your product.
When it doesn't: You want a viewer that looks complete on day one. Headless means you build the chrome, decide which controls appear, and own the final interaction design.
Note: In the same hook-and-store-shaped neighborhood, PDFSlick (@pdfslick/react, MIT) is worth a look if a Zustand-backed reactive store fits your app's data layer. Its multi-framework support (React, SolidJS, Svelte) is a differentiator, with the caveat that the last release was around six months ago at the time of writing.
Full viewer out of the box with React PDF Kit
React PDF Kit is the option that ships the toolbar and the rest of the viewer UI in the box. It's a commercial React PDF viewer component at react-pdf-kit.dev. Disclosure: I help build it.
Perpetual license. Purpose-built for React 19 and Next.js. Powered by PDF.js under the hood.
Latest 2.7.2 published June 26, 2026. 107 versions in roughly five months since the package's January 2026 launch, a fast release pace.
What ships out of the box: A complete viewer with built-in toolbar (RPLayout), search, navigation with a thumbnail panel, bbox coordinate highlighting via useElementPageContext, AcroForm form filling via Form Layers, print preview, watermarks (rendered through the same useElementPageContext primitive), and mobile responsive layout. What does not ship: an annotation plug-in for end-user highlights, comments, or stamps. React PDF Kit exposes developer-facing primitives (useHighlightContext for text and keyword highlighting, useElementPageContext for bbox-coordinate overlays), not an end-user annotation tool.
React PDF Kit minimal setup
import { RPConfig, RPProvider, RPLayout, RPPages } from '@react-pdf-kit/viewer';
export default function App() {
return (
<RPConfig>
<RPProvider src="/sample.pdf">
<RPLayout toolbar>
<RPPages />
</RPLayout>
</RPProvider>
</RPConfig>
);
}
Code sample: React PDF Kit minimal setup
That's the full viewer. Toolbar, page navigation with thumbnails, zoom, print preview, download, search across the document, mobile responsive layout. The bbox highlighting primitive is one hook away: pass useElementPageContext a coordinate set and any HTML or JSX, and the overlay renders on top of the rendered page. That's the primitive AI/RAG citation displays are built on. There's a worked AI chat example over at Building a Simple PDF AI Chat app with Next.js, React PDF Kit and OpenAI if you want to see it wired up end to end.
Docs are at react-pdf-kit.dev/docs with worked examples per feature, a hooks reference, and TypeScript types throughout. Support is commercial: license holders have a dedicated point of contact rather than community-only channels.
Trade-off: It's a commercial library. If your project can't take on a paid license, the MIT options or DIY PDF.js are the right call. Otherwise, you trade money for the weeks of build-and-debug time the other options demand, and React PDF Kit covers rendering, built-in toolbar, search, bbox highlighting, custom page elements, and AcroForm form support in a single package. You can try it at react-pdf-kit.dev.
Full feature comparison and how to choose
Here's the full picture across the four options.
| PDF.js (DIY) | wojtekmaj/react-pdf | @anaralabs/lector | React PDF Kit | |
|---|---|---|---|---|
| License | Apache 2.0 | MIT | MIT | Commercial (perpetual) |
| Latest release | 6.1.200 (Jun 2026) | 10.4.1 (Feb 2026) | 3.13.4 (Jun 2026) | 2.7.2 (Jun 2026) |
| npm weekly DLs | ~17M (engine) | ~4.8M | ~25K | Commercial |
| API style | Vanilla JS | Component | Composable primitives | Component + hooks |
| Built-in toolbar | No | No | No (headless) | Yes |
| Search | findController API | No UI | Yes | Built-in |
| Mobile responsive | DIY | Limited | Built-in | Built-in |
| Docs | Mozilla API ref | projects.wojtekmaj.pl/react-pdf (minimal) | lector-weld.vercel.app | react-pdf-kit.dev/docs |
| Support | Community | Community | Community | Commercial |
Stats verified July 2026 (versions tested in June). Weekly downloads and release versions shift, so re-check before relying on the table for a decision. PDF.js 6.0 shipped in May 2026; this comparison was built and tested on the 5.x line.
A few decision shortcuts:
Basic rendering, no UI required, MIT license, ship today. wojtekmaj/react-pdf for component familiarity if you'll write the chrome yourself. @anaralabs/lector if you want headless composable primitives and a text-layer-first architecture. @mikecousins/react-pdf if the job is literally one page on one canvas.
Full control over UI, willing to own the foundation work. PDF.js with Codex. Plan for weeks of work past the initial scaffold and budget time for the five edge cases that keep coming up. AI assistance is real but doesn't change what you have to build.
Full features (toolbar, search, bbox highlighting, form filling, mobile) shipped today, commercial license is fine. React PDF Kit. The time-to-ship answer. Search and the toolbar are already there, and bbox highlighting is one hook away.
The common mistake I see in code reviews: people pick the rendering-only library because getting a page on screen takes a few lines, so it looks done. Then they spend two months building the toolbar, search, and mobile layout the product spec asked for in the first place. The second mistake: people pick DIY PDF.js because the engine is free, underestimate the maintenance, and a year later the viewer is the most fragile part of the codebase. Pick on the requirement, not on the install command.
Wrapping up
Two weeks of building four viewers reminded me of something I keep forgetting between iterations of this article. The npm install is not the work. The work is everything you have to build on top of it to ship the viewer your PM actually asked for.
The rendering-only libraries are short on day one and long on day twenty. The DIY path with Codex is the opposite: a fast first commit, then a slow climb through print fidelity, mobile gestures, and search. The complete component skips both shapes of work in exchange for a license fee. The slot phuocng's library used to fill, mid-tier commercial with a toolbar and search out of the box, didn't disappear when the repo went read-only. It just moved.
The right pick isn't the most popular library or the one that gets a page on screen fastest. It's the option whose share of that build-on-top work matches what your team can take on. Decide that, and the table above makes the rest easy. If you're interested in React PDF Kit, full docs and live demos are available at react-pdf-kit.dev.







Top comments (0)