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
sandboxattribute 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:
README.md: Emphasizes component isolation—keep your viewer logic separate from business data fetching.packages/react-is/README.md: Use thereact-isutilities 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: 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: 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: Demonstrates proper cleanup inuseEffectfor aborting document fetch requests when the viewer unmounts, preventing memory leaks.
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
fetchor a signed URL) and pass aBlobor URL to the viewer. - Lazy-load: Document viewers are heavy. Wrap them in
React.lazyandSuspenseto 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.innerWidthand enable pinch-zoom via CSStouch-actionfor mobile compatibility. - State management: Track current page, zoom level, and annotations in React state or a state-machine library like XState. Persist this in
localStorageor your backend for cross-session continuity. - Progressive rendering: For large PDFs, render pages only when they enter the viewport using Intersection Observer. Use
requestAnimationFramefor 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-pdfprovides 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.mdandcompiler/packages/babel-plugin-react-compilerto 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →