npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

viewdoc

v0.4.0

Published

Universal React document viewer — images, PDFs, Word and Excel files with zoom, pan and scroll.

Readme

viewdoc

npm version npm downloads license types

Universal React document viewer — images, PDFs, Word documents and Excel spreadsheets, all with zoom, pan, fullscreen and a consistent toolbar.

import { DocViewer } from 'viewdoc'
import 'viewdoc/styles.css'

<DocViewer uri="https://example.com/report.pdf" />

That's it — DocViewer detects the file type from the URL and renders the right viewer. It loads whichever parser it needs (PDF/DOCX/XLSX) lazily at runtime — importing viewdoc for image-only use costs ~70KB gzipped, not the full bundle.

Install

npm install viewdoc

react and react-dom (17+) are peer dependencies — install them if your project doesn't already have them.

Don't forget the stylesheet:

import 'viewdoc/styles.css'

Next.js App Router: every component ships with a "use client" directive, so you can import and render DocViewer directly from a Server Component without wrapping it yourself.

Supported file types

| Type | Extensions | Powered by | |---|---|---| | Images | .png .jpg/.jpeg .gif .webp .svg .bmp .ico .avif | native <img> | | PDF | .pdf | pdfjs-dist | | Word | .docx | mammoth | | Excel | .xlsx .xls (legacy binary) .xlsm .csv | xlsx (SheetJS) |

Not supported: old binary .doc (pre-2007 Word) — there's no reliable way to parse it in the browser. TIFF/HEIC/RAW images also aren't supported since browsers can't decode them natively.

Components

DocViewer (recommended)

Auto-detects the file type and renders the matching viewer.

<DocViewer uri="https://example.com/invoice.xlsx" fileName="invoice.xlsx" />

Detection reads the URL's path (ignoring query strings, so presigned S3/CloudFront URLs work correctly). If your URL has no reliable extension (e.g. an opaque storage key), force it:

<DocViewer uri="https://example.com/blob/9f8a2b" type="pdf" />

type also accepts a MIME type ("application/pdf", "image/png", etc.) if that's what you have on hand.

If detection fails and no type is given, DocViewer renders a fallback node (or a small default message):

<DocViewer uri={file.url} fallback={<p>Can't preview this file type.</p>} />

Individual viewers

ImageViewer is cheap (no extra dependencies) and lives on the main entry. PdfViewer, DocxViewer and XlsxViewer each pull in a sizeable parser (pdfjs-dist, mammoth, xlsx respectively), so they live on their own subpaths — this keeps viewdoc's main entry small for apps that only need images, and DocViewer loads whichever one it needs lazily at runtime instead of bundling all three upfront.

import { ImageViewer } from 'viewdoc'
import { PdfViewer } from 'viewdoc/pdf'
import { DocxViewer } from 'viewdoc/docx'
import { XlsxViewer } from 'viewdoc/xlsx'

<ImageViewer uri="https://example.com/photo.jpg" />
<PdfViewer uri="https://example.com/report.pdf" />
<DocxViewer uri="https://example.com/letter.docx" />
<XlsxViewer uri="https://example.com/budget.xlsx" />

Props

DocViewer and all four viewers share the same prop shape (DocViewer just adds type and fallback on top):

| Prop | Type | Default | Description | |---|---|---|---| | uri | string | required | URL or data URI of the file | | fileName | string | — | Alt text / suggested download filename | | className / style | — | — | Applied to the outer wrapper | | width / height | number \| string | 1200 / 700 | Viewer size (px number or any CSS size) | | minScale / maxScale / zoomStep | number | varies by viewer | Zoom range and increment | | enableZoomControls | boolean | true | Show zoom out/in/reset buttons | | enableWheelZoom | boolean | true | Ctrl/Cmd + scroll wheel to zoom | | enablePan (ImageViewer only) | boolean | true | Click-drag-release panning of the image | | enableFullscreen | boolean | true | Show fullscreen toggle | | enableDownload | boolean | true | Show download button | | enableIframeFallback (PdfViewer only) | boolean | true | Fall back to a plain <iframe> when the PDF can't be fetched (usually a CORS-blocked server) instead of showing an error | | onDownload | () => void | — | Custom download handler; defaults to downloading uri | | theme | ViewerTheme | — | Override colors/radii (see below) | | floating | boolean | true | Render as a centered, draggable floating window vs. an embedded block that fills the parent | | windowDraggable | boolean | true | When floating, allow dragging via the toolbar's grip icon | | defaultPosition | { x: number; y: number } | centered in viewport | Initial position when floating |

Every boolean defaults to true — pass false to turn a feature off. Nothing needs to be configured to get a fully working viewer.

Theming

Pass a theme object to override the default dark look. Any field you omit keeps its default:

<DocViewer
  uri={file.url}
  theme={{
    background: '#0b1220',
    toolbarBackground: '#111827',
    toolbarBorderColor: '#1f2937',
    toolbarButtonHoverBackground: '#1f2937',
    textColor: '#93c5fd',
    borderRadius: '16px',
  }}
/>

Available fields: background, toolbarBackground, toolbarBorderColor, toolbarButtonHoverBackground, textColor, borderRadius, toolbarButtonRadius.

Embedded vs. floating mode

By default every viewer opens as a floating window: centered on screen, draggable by its toolbar grip icon, sized 1200×700. This is meant for "preview this file" use cases (e.g. clicking a file in a list).

If you want the viewer to sit inline in your layout instead — filling whatever container you put it in — set floating={false}:

<div style={{ width: 600, height: 400 }}>
  <DocViewer uri={file.url} floating={false} />
</div>

In non-floating mode, the viewer still renders at its width/height (default 1200×700) and centers itself inside the parent container.

Interaction reference

  • Zoom: +/− buttons, click the % label or the reset icon to return to 100%, hold Ctrl/Cmd and scroll, or pinch with two fingers on touch devices
  • Pan: click-drag the content directly (images), or click-drag/scroll anywhere on the canvas (PDF, DOCX, XLSX — these use native scrolling under the hood, so the mouse wheel always works too)
  • Move the window: click-drag the small grip icon (⊹) at the left of the toolbar (only shown when floating is true)
  • Fullscreen: toggle button on the right of the toolbar
  • Download: button next to fullscreen; downloads uri unless you pass onDownload
  • Keyboard: every toolbar button is reachable via Tab and activates with Enter/Space; Tab once more to focus the document content, then use arrow keys to pan (images) or arrow keys/Page Up/Down/Space (PDF, DOCX, XLSX — native browser scroll on the focused container)

When a PDF's server doesn't allow CORS

PdfViewer needs to fetch() the raw PDF bytes in JavaScript to parse and render them (that's what gives you the shared toolbar, zoom, and virtualization) — this only works if the server hosting the file sends the right CORS headers. This is the same requirement every canvas-based PDF viewer has (not specific to viewdoc); a plain <iframe src="..."> works without CORS because it's just navigation, not a JS-readable fetch.

If the file's server doesn't send CORS headers (common with third-party links, or your own storage before you've configured it), PdfViewer automatically falls back to displaying the PDF via a plain <iframe> using the browser's native PDF viewer — you lose the custom toolbar/zoom/virtualization for that file, but the user still sees the document instead of an error. Disable this with enableIframeFallback={false} if you'd rather show the error message instead.

For files on your own storage (S3, GCS, your own server, etc.), the better fix is to configure CORS on that storage once — it then works with the full PdfViewer experience for every file, permanently. For S3, add this to the bucket's Permissions → CORS configuration:

[
  {
    "AllowedHeaders": ["*"],
    "AllowedMethods": ["GET", "HEAD"],
    "AllowedOrigins": ["*"],
    "ExposeHeaders": ["Content-Length", "Content-Range", "ETag"],
    "MaxAgeSeconds": 3600
  }
]

Replace "AllowedOrigins": ["*"] with your app's actual domain for tighter security.

Known limitations

  • Old binary .doc/legacy Word formats before 2007 aren't supported (only .docx)
  • The Excel table view is read-only — no cell editing or selection
  • XlsxViewer falls back to CSV parsing for unrecognized content (this is intentional — SheetJS supports .csv), so pointing it at a non-spreadsheet file won't always error; it may render the raw text as a single cell instead
  • DocxViewer and XlsxViewer render content via dangerouslySetInnerHTML using HTML generated from the file itself (by mammoth/xlsx); that HTML is passed through DOMPurify before rendering to strip scripts/event handlers/dangerous URI schemes (e.g. a javascript: hyperlink target embedded in a malicious file)
  • PdfViewer loads its PDF.js worker script from a CDN (jsDelivr) pinned to the exact pdfjs-dist version bundled with viewdoc, rather than resolving it as a local module path. This avoids an "API version does not match the Worker version" error that happens when your app also depends on pdfjs-dist through another library (e.g. react-pdf) at a different version — bundlers commonly fail to disambiguate the two nested copies and load the wrong worker. The tradeoff is that PdfViewer requires network access to jsdelivr.net; if that's blocked (e.g. an offline app or a strict CSP), set pdfjsLib.GlobalWorkerOptions.workerSrc yourself to a self-hosted copy before rendering any PdfViewer
  • PdfViewer only renders real canvases for pages currently visible (adapting to zoom level, so zooming out to see many pages at once doesn't leave any of them stuck as blank placeholders), plus a ±3 page buffer; other pages show a lightweight sized placeholder, so memory and initial render time stay low regardless of document length
  • XlsxViewer caps rendered rows at maxRows (default 2000) per sheet, since the table has no virtualization either — a 10,000-row sheet took ~9 seconds to render unbounded vs. ~1.7 seconds capped. Raise maxRows if you need more and can accept the tradeoff, or download the file for the full data
  • The Fullscreen API isn't supported on iOS Safari for anything other than <video> elements — the fullscreen button is automatically hidden there rather than showing a control that can't work
  • The xlsx dependency is installed from SheetJS's own CDN rather than the npm registry — SheetJS stopped publishing security-patched releases to npm after 0.18.5, which has known unpatched CVEs (prototype pollution, ReDoS). This is SheetJS's own officially documented install method for getting patched versions

License

MIT