Skip to main content
If you’re building a React app that needs to render PDFs, you’ve got a buffet of choices—from lightweight, OSS components to full‑blown commercial SDKs with redaction, signing, and real‑time collaboration. This Mintlify edition keeps everything in standard Markdown (tables + fenced code) and uses callouts where helpful.

TL;DR

Quickest embed: <iframe> / <object> (zero JS, zero features). Most popular OSS: react-pdf (PDF.js under the hood; easy). Headless control: use pdfjs-dist directly for custom UI/virtualization. Feature‑heavy: Apryse WebViewer, PSPDFKit, Foxit Web SDK. Next.js: client‑only viewer, load the worker, enable byte‑range. :::

Options at a glance

Libraries like pdf-lib or PDFKit focus on creation/manipulation, not end‑user viewing.

How PDF rendering works (why it matters)

  • PDF.js (canvas/SVG): Most OSS viewers rely on Mozilla’s PDF.js to parse and render each page to <canvas> (or sometimes SVG). Canvas is typically faster and more compatible on mobile; SVG may have better selectable text but can bloat the DOM.
  • Virtualization: Rendering only visible pages prevents memory blowups—vital for 500+ page docs on mobile.
  • Workers: Offload parsing to a Web Worker (pdf.worker.js). Without it, the main thread janks.
  • Range Requests: Enable HTTP byte‑range so users can open page 300 without downloading everything.

1) The simplest path: embed the browser’s viewer

Pros: zero JS; browser handles zoom/print. Cons: inconsistent UI across browsers; limited features; can be blocked by X-Frame-Options or strict CSP. Tip: For public files, link‑fallback to the native viewer when JS fails. Install
Minimal viewer
Why choose it
  • Componentized API for React.
  • Built‑in text selection and (basic) form support.
  • Good community docs.
Watch for
  • Disable SSR for the viewer component (Next.js).
  • Large files: render current + nearby pages only to avoid memory spikes.
:::note Virtualizing pages (concept) Render only pages that are visible or near the viewport. Keep a small buffer (for example, ±1 page) for smooth scrolling. :::

3) Headless: PDF.js (pdfjs-dist) with custom UI

Install
Skeleton
Why headless
  • Full control over layout, virtualization, page cache, search indexing, and UI.
  • Tailor performance for massive docs.
Caveats
  • You build the toolbar, thumbnails, text selection, and accessibility affordances.

4) Full‑featured commercial SDKs

When you need annotations, comments, collaboration, e‑signing, redaction, measurement, forms, or Office file viewing, consider:
  • Apryse WebViewer (formerly PDFTron)
  • PSPDFKit for Web
  • Foxit Web SDK
Why: battle‑tested rendering, enterprise features, support SLAs, and consistent UX across browsers. Cost trade‑off: higher licensing; heavier bundles. Many offer cloud/on‑prem options and per‑seat or usage licensing.

Next.js / Vite integration notes

  • Workers must be served correctly. If hosting yourself, copy pdf.worker.min.js to public/ and set workerSrc accordingly.
  • Range Requests: serve PDFs via a CDN/origin that supports Accept-Ranges: bytes.
  • CORS: if loading cross‑origin, enable CORS and set Content-Type: application/pdf.
  • Printing: PDF.js has its own print flow; test on Safari and iOS specifically.

Accessibility and i18n

  • Provide keyboard navigation (page up/down, space, arrows).
  • Ensure the text layer is present for selection and screen readers.
  • Add aria labels for toolbar controls.
  • Support RTL locales and mixed‑language content.

Performance checklist

  • Use virtualized pages (render only what’s visible).
  • Cache rendered canvases; reuse when zooming.
  • Preload next/prev page on idle.
  • Keep scale < 2.0 on mobile to avoid massive canvases.
  • Debounce zoom/scroll events.
  • Offload heavy work to Web Workers.

Security checklist

  • Don’t execute PDF contents; treat links as untrusted.
  • Sanitize mailto: / block javascript: links; open external links with rel="noopener noreferrer".
  • Respect Content-Security-Policy and consider frame-ancestors / X-Frame-Options when embedding.

Choosing: a decision tree

  1. Just preview a PDF? Try <object>/<iframe> (fast). If you need consistent zoom/print, use react-pdf.
  2. Custom UI & performance tuning? Build with pdfjs-dist directly; add virtualization.
  3. Annotations/forms/signing/redaction? Choose a commercial SDK (Apryse/PSPDFKit/Foxit).
  4. Massive files / mobile heavy? Headless PDF.js + virtualization + worker + CDN with byte‑range.

Gotchas & debugging

  • Blank pages: worker not found → double‑check workerSrc path.
  • Slow first paint: no range requests → enable Accept-Ranges, or load from same origin.
  • CORS errors: add Access-Control-Allow-Origin and correct Content-Type.
  • SSR crash: dynamic import with ssr:false and guard all window/document access.
  • Huge memory: render 1–3 pages around viewport; recycle canvases.

Quick recommendations

  • General product UI: react-pdf + light toolbar; add virtualization when PDFs exceed ~200 pages.
  • Heavily customized experience: pdfjs-dist + your own UI/virtualization.
  • Enterprise doc workflows: Apryse/PSPDFKit/Foxit—budget permitting.