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

> Implement a React document viewer using iframes, PDF.js, or SaaS embeds. Explore 6 production-ready approaches for PDFs and other documents to meet your app's needs.

- Repository: [Meta/react](https://github.com/facebook/react)
- Tags: tutorial
- Published: 2026-02-16

---

**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`](https://github.com/facebook/react/blob/main/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:

- **[`README.md`](https://github.com/facebook/react/blob/main/README.md)**: Emphasizes component isolation—keep your viewer logic separate from business data fetching.
- **[`packages/react-is/README.md`](https://github.com/facebook/react/blob/main/packages/react-is/README.md)**: Use the `react-is` utilities to type-check that children passed to your viewer are valid React elements, preventing runtime errors in polymorphic wrappers.
- **[`packages/react-devtools-core/README.md`](https://github.com/facebook/react/blob/main/packages/react-devtools-core/README.md)**: Instrument your viewer with proper display names and debug IDs to trace state updates in React DevTools when debugging page navigation or zoom state.
- **[`scripts/eslint-rules/README.md`](https://github.com/facebook/react/blob/main/scripts/eslint-rules/README.md)**: Enforce consistent hook usage and naming conventions in viewer components to prevent stale closures in pagination controls.
- **[`compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffect-snap-test.expect.md`](https://github.com/facebook/react/blob/main/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useEffect-snap-test.expect.md)**: Demonstrates proper cleanup in `useEffect` for aborting document fetch requests when the viewer unmounts, preventing memory leaks.

## Implementation Examples

### Simple iframe Viewer

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

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

```tsx
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`](https://github.com/facebook/react/blob/main/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`](https://github.com/facebook/react/blob/main/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`](https://github.com/facebook/react/blob/main/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.