How to Implement a React Document Viewer: 6 Production-Ready Approaches

A react document viewer can be implemented using native browser <iframe> elements for simple PDFs, Mozilla's PDF.js via react-pdf for custom UIs, or third-party SaaS embeds for enterprise features, depending on your fidelity, security, and bundle-size requirements.

React is a library for building user interfaces, not a document rendering engine—so the facebook/react repository contains no native viewer components. To display PDFs, Word documents, or spreadsheets in a react document viewer, you must integrate browser APIs, JavaScript libraries, or external services that handle the actual rendering. This guide covers six production-ready patterns, referencing implementation details from the React source code to optimize performance and state management.

Native Browser Rendering with iframes

The simplest approach uses the browser's built-in PDF capabilities via an <iframe> or <embed> element. You pass a public or signed URL to the file, and the browser handles the rendering.

  • No dependencies required, works out-of-the-box in modern browsers.
  • Limited UI control: You cannot customize toolbars, implement page thumbnails, or add annotations.
  • Security: Use the sandbox attribute to restrict scripts (sandbox="allow-scripts allow-same-origin") when displaying untrusted content.

This pattern is ideal when you need to display static PDFs or images without interaction, and bundle size is a primary concern.

PDF.js Integration via react-pdf

For a fully customizable react document viewer, wrap Mozilla’s PDF.js library using the react-pdf package. This renders PDF pages to <canvas> elements and exposes navigation, zoom, and text-selection APIs.

  • Bundle size: Approximately 300 KB gzipped, plus the worker script.
  • Rendering: CPU-intensive for large documents; use Web Workers (PDF.js defaults to this) to avoid blocking the main thread.
  • Text layer: PDF.js supports a hidden text layer over the canvas for accessibility and search functionality.

According to patterns in compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffect-snap-test.expect.md, you should fetch document blobs inside useEffect hooks to handle asynchronous loading without suspending the component tree unnecessarily.

External Service Embeds (Office Online and Google Docs)

For Word, Excel, or PowerPoint files, embed Microsoft Office Online or Google Docs viewers via iframes. These services convert and render the document on their servers.

  • Fidelity: Near-native rendering with automatic updates and collaboration features.
  • Privacy concerns: Documents are sent to third-party servers, requiring authentication and compliance review.
  • Offline capability: None—requires persistent internet access.

Use this approach for enterprise applications already integrated with Microsoft 365 or Google Workspace, where accuracy matters more than data sovereignty.

Open-Source Multi-Format Viewers

Libraries like ViewerJS or WebODF support ODT, ODS, DOC, XLS, and PPT formats by internally converting them to PDF or ODF. These are suitable when you cannot rely on external SaaS solutions but need broader format support than PDF.js alone provides.

  • Pre-conversion requirement: Files often must be converted server-side to formats the viewer can parse.
  • Limited annotations: Basic viewing is supported, but advanced markup features are typically missing.

Commercial SaaS SDKs (PDFTron, PSPDFKit)

For mission-critical applications requiring redaction, form filling, digital signatures, or high-performance rendering, embed a paid SDK that provides a pre-built react document viewer component.

  • Vendor lock-in: Licensing costs and proprietary APIs.
  • Performance: Optimized rendering engines with cross-platform support.
  • Bundle impact: Larger initial download sizes; lazy-load these components aggressively.

React Internals and Architectural Patterns

When building a custom viewer, leverage patterns from the facebook/react source to ensure stability and performance:

Implementation Examples

Simple iframe Viewer

import React from 'react';

interface IframeViewerProps {
  fileUrl: string; // a public or signed URL pointing to a PDF
}

export const IframeViewer: React.FC<IframeViewerProps> = ({fileUrl}) => (
  <iframe
    src={fileUrl}
    title="Document viewer"
    style={{border: 'none', width: '100%', height: '100vh'}}
    sandbox="allow-scripts allow-same-origin"
  />
);

Lazy-Loaded PDF.js with react-pdf

import React, {useState, Suspense, lazy} from 'react';
import {Document, Page, pdfjs} from 'react-pdf';

// Ensure PDF.js worker is loaded
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.min.js`;

const PDFViewer = ({fileUrl}: {fileUrl: string}) => {
  const [numPages, setNumPages] = useState(0);
  const [page, setPage] = useState(1);
  const [scale, setScale] = useState(1.0);

  const onDocumentLoadSuccess = ({numPages}: {numPages: number}) => {
    setNumPages(numPages);
    setPage(1);
  };

  return (
    <div>
      <div style={{marginBottom: 8}}>
        <button onClick={() => setPage(p => Math.max(p - 1, 1))}>Prev</button>
        <span>{page} / {numPages}</span>
        <button onClick={() => setPage(p => Math.min(p + 1, numPages))}>Next</button>
        <button onClick={() => setScale(s => s + 0.25)}>Zoom +</button>
        <button onClick={() => setScale(s => Math.max(s - 0.25, 0.25))}>Zoom -</button>
      </div>

      <Document file={fileUrl} onLoadSuccess={onDocumentLoadSuccess}>
        <Page pageNumber={page} scale={scale} />
      </Document>
    </div>
  );
};

// Lazy-load the viewer only when needed
export const LazyPDFViewer = lazy(() => import('./PDFViewer'));

export const DocumentContainer: React.FC<{url: string}> = ({url}) => (
  <Suspense fallback={<div>Loading viewer…</div>}>
    <LazyPDFViewer fileUrl={url} />
  </Suspense>
);

Microsoft Office Online Embed

import React from 'react';

interface OfficeViewerProps {
  fileUrl: string; // public URL to a .docx file
}

export const OfficeViewer: React.FC<OfficeViewerProps> = ({fileUrl}) => {
  const officeSrc = `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(
    fileUrl,
  )}`;

  return (
    <iframe
      src={officeSrc}
      title="Office document"
      style={{border: 0, width: '100%', height: '100vh'}}
      sandbox="allow-scripts allow-same-origin allow-forms"
    />
  );
};

Performance and Security Best Practices

  • Separate concerns: Isolate the viewer component from business logic. Use a container component to fetch the document (via fetch or a signed URL) and pass a Blob or URL to the viewer.
  • Lazy-load: Document viewers are heavy. Wrap them in React.lazy and Suspense to prevent bloating the initial bundle.
  • Secure delivery: Serve documents over HTTPS using short-lived signed URLs (AWS S3 presigned, Azure SAS). Enforce Content Security Policy (CSP) headers to restrict what embedded iframes can load.
  • Responsive design: Size PDF.js canvas elements to window.innerWidth and enable pinch-zoom via CSS touch-action for mobile compatibility.
  • State management: Track current page, zoom level, and annotations in React state or a state-machine library like XState. Persist this in localStorage or your backend for cross-session continuity.
  • Progressive rendering: For large PDFs, render pages only when they enter the viewport using Intersection Observer. Use requestAnimationFrame for smooth zoom animations.
  • Accessibility: Implement keyboard shortcuts (Page Up/Down for navigation) and ensure the canvas has an associated text layer for screen readers.

Summary

  • React has no built-in viewer; you must integrate browser APIs or third-party libraries to render documents.
  • <iframe> is sufficient for simple PDFs with no UI customization.
  • react-pdf provides the most flexible react document viewer experience for PDFs, supporting custom toolbars and text selection.
  • Office Online and Google Docs offer the best format compatibility for Office documents but require external service dependencies.
  • Enterprise SaaS SDKs deliver advanced features like redaction and digital signatures but introduce licensing costs and vendor lock-in.
  • Leverage React patterns from packages/react-devtools-core/README.md and compiler/packages/babel-plugin-react-compiler to handle async loading, state management, and debugging effectively.

Frequently Asked Questions

Does React include a built-in document viewer?

No. The facebook/react library focuses on UI rendering and state management. As noted in the repository's README.md, React provides the component model and hooks (like useEffect for data fetching), but you must integrate external libraries such as PDF.js or browser-native iframes to display documents.

How do I prevent PDF rendering from blocking the UI?

Use Web Workers for PDF parsing (PDF.js does this by default) and lazy-load the viewer component with React.lazy and Suspense. For very large documents, implement virtualized rendering—only drawing pages when they scroll into view—to minimize memory and CPU usage on the main thread.

Is it safe to embed documents using Office Online or Google Docs?

These services require uploading files to third-party servers, which may violate data privacy policies for sensitive content. For confidential documents, use client-side libraries like react-pdf or self-hosted solutions, and serve files via HTTPS with signed URLs that expire quickly to prevent unauthorized access.

What is the best way to handle unsupported file formats in a React viewer?

Check the file extension or MIME type before rendering. Use packages/react-is/README.md patterns to validate that your viewer component receives the correct props. For unsupported types, either convert them server-side to PDF (using tools like LibreOffice in headless mode) or provide a download link as a fallback.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →