Render PDF · Word · Excel · PowerPoint · image · video · audio — entirely in the browser. No Microsoft Online Viewer iframe. No Google Docs preview. No "we upload your file to our server first." The bytes never leave the tab.
- 🔒 Zero data exfiltration. Documents are parsed in the user's browser. No backend round-trip, no third-party iframe, no telemetry.
- 🪶 Pay for what you use. Every format lives in its own npm package; tree-shake or dynamic-import only what your app actually needs.
- ⚛️ React & Next.js native.
'use client', drop in<Viewer />, done. The hook works with Suspense and the App Router. - 🧩 Pluggable. A renderer is 30 lines of TypeScript implementing one interface. Add a new format in an afternoon.
- 🛡️ Hardened by default. DOMPurify-sanitized HTML, zip-slip & zip-bomb defenses, size caps, no
eval, nodangerouslySetInnerHTMLof user content. - 🔏 Signed releases. Every npm version ships with SLSA provenance via GitHub OIDC trusted publishing — verifiable build origin, no tokens involved.
Drop any of these onto the page — it renders locally:
- 📄
*.pdf - 📝
*.docx*.doc - 📊
*.xlsx*.xls*.csv*.tsv*.ods - 📽️
*.pptx - 🖼️
*.png*.jpg*.gif*.webp*.svg*.avif*.bmp - 🎞️
*.mp4*.webm*.ogg*.mov*.mkv - 🎵
*.mp3*.wav*.flac*.aac*.m4a*.opus - 💻
*.txt*.md*.json*.yamland every common source-code extension
| Package | Version | Min+gzip | Description |
|---|---|---|---|
@microscope-js/core |
Framework-agnostic registry + mount() |
||
@microscope-js/utils |
Source / MIME / sanitize helpers | ||
@microscope-js/react |
React / Next.js adapter, <Viewer /> |
||
@microscope-js/renderer-pdf |
PDF via pdfjs-dist |
||
@microscope-js/renderer-docx |
Word .docx via mammoth + DOMPurify |
||
@microscope-js/renderer-xlsx |
Excel .xlsx/.xls/.csv via SheetJS |
||
@microscope-js/renderer-pptx |
PowerPoint .pptx via JSZip |
||
@microscope-js/renderer-image |
png / jpg / gif / webp / svg / avif / bmp | ||
@microscope-js/renderer-video |
mp4 / webm / ogg / mov | ||
@microscope-js/renderer-audio |
mp3 / wav / flac / ogg / aac | ||
@microscope-js/renderer-text |
Plain text / source code |
pnpm add @microscope-js/react @microscope-js/renderer-pdf @microscope-js/renderer-image'use client';
import { Viewer, useRegistry } from '@microscope-js/react';
import { pdfRenderer } from '@microscope-js/renderer-pdf';
import { imageRenderer } from '@microscope-js/renderer-image';
export default function FilePreview({ file }: { file: File }) {
const registry = useRegistry([pdfRenderer, imageRenderer]);
return <Viewer source={file} registry={registry} style={{ height: 600 }} />;
}<Viewer /> accepts a File, Blob, ArrayBuffer, Uint8Array, URL, or a string URL — anything you have on hand.
import { createRegistry, mount } from '@microscope-js/core';
import { pdfRenderer } from '@microscope-js/renderer-pdf';
const registry = createRegistry([pdfRenderer]);
const handle = await mount({
source: fileInput.files[0],
container: document.getElementById('viewer')!,
registry,
});
// later...
handle.destroy();const { containerRef, loading, error, handle } = useViewer({ source, registry });
return (
<div ref={containerRef} className="my-toolbar-frame">
{handle?.capabilities?.pageCount /* renderer-specific extras */}
</div>
);| Concern | MS Office iframe | Google Docs viewer | docx-preview / etc. | microscope-js |
|---|---|---|---|---|
| File leaves the device | ✅ uploads to Microsoft | ✅ uploads to Google | ❌ | ❌ |
| Works offline | ❌ | ❌ | partial | ✅ |
| Formats covered | Office only | Office + PDF | one per library | PDF + Office + media + image + code, one API |
| Bundle cost | iframe (heavy) | iframe (heavy) | one library, monolithic | ~1–5 KB core + per-format packages, tree-shakable |
| React + Next.js / SSR | varies | ✅ first-class, SSR-safe | ||
| Provenance / signing | n/a | n/a | usually unsigned | ✅ SLSA + npm trusted publisher |
| License | proprietary | proprietary | mixed | MIT |
┌─────────────────────────────────┐
│ @microscope-js/core │
│ Renderer interface + registry │
└──────────────┬──────────────────┘
│
┌───────────────────────┼──────────────────────────┐
│ │ │
┌──────▼──────┐ ┌───────▼────────┐ ┌───────▼─────────┐
│ renderer-pdf│ ... │ renderer-image │ ... │ renderer-docx │
└─────────────┘ └────────────────┘ └─────────────────┘
│ │ │
└───────────────────────┼──────────────────────────┘
│
┌──────────────▼──────────────────┐
│ @microscope-js/react │
│ <Viewer />, useViewer() │
└─────────────────────────────────┘
The core package defines exactly one interface (Renderer) and one registry. Every format implementation lives in its own npm package and is loaded via dynamic import() so first-paint pays only for core + react.
| Threat | Mitigation |
|---|---|
| HTML in Office documents | All renderer output passes through DOMPurify with a hardened allowlist |
Zip-slip (.. in DOCX/XLSX/PPTX) |
Archive entries with parent-dir or absolute paths are rejected before extraction |
| Zip bombs | ByteBudget caps total uncompressed bytes (default 512 MB) |
| Oversized PDFs | Configurable maxBytes / maxPages per renderer |
| SVG / XML XSS | SVGs are mounted via <img> (no script execution); inline SVG is sanitized |
| Dynamic JS | No eval, no new Function, no dangerouslySetInnerHTML of user content — CI greps for these |
| Supply-chain | Every published tarball is signed with SLSA provenance via npm OIDC trusted publishing |
See SECURITY.md to report a vulnerability privately.
pnpm install
pnpm build # build all packages
pnpm test # vitest across the monorepo
pnpm typecheck # tsc --noEmit, per package
pnpm lint # biome check
pnpm demo # localhost:3000
pnpm docs # TypeDoc -> ./docs-sitemicroscope-js/
├── packages/
│ ├── core/ # Renderer interface + registry
│ ├── utils/ # source / sniff / sanitize / zip-safety helpers
│ ├── react/ # <Viewer /> + useViewer + useRegistry
│ ├── renderer-pdf/
│ ├── renderer-docx/
│ ├── renderer-xlsx/
│ ├── renderer-pptx/
│ ├── renderer-image/
│ ├── renderer-video/
│ ├── renderer-audio/
│ └── renderer-text/
├── apps/
│ └── demo/ # Next.js → GitHub Pages
└── .github/workflows/ # CI · release · pages · security · dependabot
- Pixel-perfect PPTX (shape + image layout, not just text)
- EPUB / FB2 ebook renderer
- RTF / ODT renderer
- Optional Web Worker for office formats (offload main-thread parsing)
- Plugin: in-viewer search across PDF / DOCX
- Plugin: annotation / highlight overlay
- Solid / Vue / Svelte adapters
Got an idea? Open a discussion.
PRs are welcome — see CONTRIBUTING.md. Adding a new format is a copy of packages/renderer-image with a new Renderer object. The registry picks it up automatically.
If this saved you a backend, a star on GitHub goes a long way. ⭐
MIT © microscope-js contributors
Built with pnpm · tsup · Changesets · Biome · Vitest · TypeDoc. Published with npm Trusted Publishers + SLSA provenance.