OpenDocViewer is a client-side React + Vite document viewer for PDF, TIFF, and common raster image formats. The project is designed to be deployable as static files, with optional companion log servers for operational tracing and print auditing.
The codebase now has three documentation layers:
README.md— product-level overview, setup, deployment, and developer workflowCONTRIBUTING.md— repository conventions, naming rules, and review checklistdocs-src/— deeper architecture and runtime-configuration notes for maintainers
- Features
- Documentation map
- Architecture overview
- Bootstrap modes
- Requirements
- Quick start
- Build, preview, and generated docs
- Release workflow
- Hosting and deployment
- Runtime configuration
- Printing
- Logging
- Operations and deployment
- Project structure
- Development conventions
- Troubleshooting notes
- License
- Document formats
- PDF via
pdfjs-dist - TIFF via
utif2 - Common image formats such as JPG and PNG
- PDF via
- Viewer behavior
- Fit-to-page and fit-to-width sticky zoom modes
- Explicit zoom controls, typed zoom percentage, and 1:1 mode
- Optional comparison view with independent per-pane post-zoom
- Deterministic thumbnail pane with keyboard-friendly selection, compare badges, and width controls
- Lazy full-page / thumbnail rendering with bounded cache sizes
- Basic visual image adjustments for raster pages (rotation, brightness, contrast)
- Printing
- Current page, all pages, range, and explicit sequence printing
- Optional print-header overlay with template tokens
- Runtime flexibility
- Runtime config through
public/odv.config.js - Configurable large-document loading thresholds, adaptive memory heuristics, prefetch concurrency, and cache limits
- Adaptive browser temp storage (memory -> IndexedDB) for original source bytes
- Optional persisted page-asset storage so rendered full pages / thumbnails can be reused without re-rendering
- Multiple bootstrap sources: demo, URL parameters, session token, parent window, JS API
- Runtime config through
- Optional operational support
- User print logging
- System logging
- Performance overlay for troubleshooting
Use the following files depending on what you are trying to understand:
README.md- product scope, setup, deployment, and quick orientation
CONTRIBUTING.md- naming conventions,
js/jsxpolicy, review expectations
- naming conventions,
docs-src/architecture.md- module responsibilities and request/data flow through the app
docs-src/runtime-configuration.md- runtime config loading order, override rules, and deployment notes
docs-src/log-servers.md- logging endpoint contracts, retention, proxy patterns, and security assumptions
docs-src/deploy-ops.md- IIS hosting, proxy deployment, cache rules, and operational checklists
docs-src/printing.md- print pipeline design and the responsibilities of the print helper modules
docs-src/integrations.md- bootstrap modes, host payload shapes, and where integration logic belongs
docs-src/customer-performance-profile.md- rationale for the high-memory, fast-feeling customer profile bundled in this build
src/types/jsdoc-types.js- shared JSDoc-only callback/type aliases used across the UI
At a high level the application is split into five layers:
- Boot and startup
src/app/bootConfig.jssrc/app/AppBootstrap.jsxsrc/integrations/*
- Application shell and providers
src/app/OpenDocViewer.jsxsrc/contexts/*
- Document loading and rendering
src/components/DocumentLoader/*src/components/DocumentRender.jsxsrc/components/ImageRenderer.jsxsrc/components/CanvasRenderer.jsx
- Viewer interaction
src/components/DocumentViewer/*src/components/DocumentToolbar/*src/hooks/*src/utils/*
- Operational support
src/logging/*server/*public/odv-admin.html
For a deeper walkthrough, see docs-src/architecture.md.
The viewer can start in several different ways. src/integrations/bootstrapRuntime.js probes them in priority order.
- Parent page
- same-origin host page passes bootstrap data through
window.parent
- same-origin host page passes bootstrap data through
- Session token
- bootstrap data is supplied through a URL token/payload
- URL parameters
- legacy pattern mode using folder + extension + end number
- JS API
- host code calls
window.ODV.start(...)
- host code calls
- Demo mode
- fallback that builds a sample source list from files in
public/
- fallback that builds a sample source list from files in
This lets the same build work for demo use, embedded use, and host-integrated deployments.
- Node.js 20.19+ or 22.12+
- Primary target browsers: Microsoft Edge and Google Chrome (Chromium)
- Firefox may work for basic viewing, but it is not the primary support target and may differ in diagnostics and some HTML input-validation behavior
- Safari is not the primary operational target
- Static hosting for the built frontend
- Optional: Node.js runtime for the log servers under
server/(same version floor as above is recommended)
git clone https://github.com/Optimal2/OpenDocViewer.git
cd OpenDocViewer
npm install
npm run devOpen the local Vite URL, typically http://localhost:5173.
# Production build
npm run build
# Local preview of dist/
npm run preview
# JSDoc site (generated into ./docs/)
npm run docThe generated JSDoc output is not intended to be committed. The hand-written source documentation lives in README.md, CONTRIBUTING.md, and docs-src/.
For a normal release candidate, validate locally first:
npm ci
npm run lint
npm run build
npm run docThen run the PowerShell helper from the repo root on Windows:
.\release.ps1The release helper now runs the same local validation steps (lint, build, doc) before it creates a release commit. It does not edit SECURITY.md, so update that file manually before invoking a release when the supported-version matrix or release-context section needs to change. After validation passes, it will:
- stage and commit the current working changes
- run
npm version <patch|minor|major>to create the version-bump commit and Git tag - push the branch and tag to
origin
The pushed tag triggers .github/workflows/release.yml, which builds the production bundle and generated docs, packages them as zip files, and publishes the GitHub release assets.
Use GitHub Desktop for review if you want, but you do not need to create or push the release commits manually unless you explicitly prefer that workflow.
The frontend is static. Deploy dist/ to IIS, Nginx, Apache, S3/CloudFront, or another static host.
Important deployment rules:
- Serve
index.htmlas the SPA fallback for unknown routes. - Do not long-cache
index.htmlorodv.config.js. - Fingerprinted assets under Vite output can be long-cached.
- If using the log servers, proxy them separately rather than mixing them into the static host process.
IIS-specific helper files and scripts are included in:
public/web.configIIS-ODVProxyApp/scripts/
Runtime config is loaded before the React application starts.
Loading order:
- optional
odv.site.config.js - required
odv.config.js - React bootstrap from
src/index.jsx
The config covers areas such as:
- diagnostics
- i18n defaults and translation loading (now cache-busted automatically per build)
- a configured
i18n.defaultinodv.site.config.jsnow wins over browser/OS language detection
- a configured
- print-header overlay
- user print logging
- system logging
- application base path/base href
- optional integration-adapter metadata alias mappings
- raw metadata records remain preserved even when no aliases are configured
- large-document loading (
documentLoading) for warnings, temp storage, lazy rendering, and cache limits
For deployment and precedence details, see docs-src/runtime-configuration.md.
When the performance overlay is disabled, OpenDocViewer also disables the overlay-specific runtime polling and snapshot work instead of merely hiding the panel.
The viewer now exposes three explicit runtime modes under documentLoading.mode:
performance— fetch in a ticket-safe sequential order, warm pages aggressively, prefer workers for raster/TIFF rendering, and keep larger in-memory cachesmemory— stay conservative, render lazily around the viewport, persist more aggressively, and evict RAM-backed object URLs soonerauto— start close toperformance, then degrade one-way towardmemorywhen page count or browser memory pressure crosses configured thresholds
The runtime pipeline is now intentionally hybrid rather than purely eager or purely lazy:
- fetch original source files early, optionally with true sequential fetch order for short-lived ticket links
- store original bytes in memory or IndexedDB depending on the active mode and current pressure stage
- analyze page counts in stable order from the prefetched source blob
- insert placeholders immediately so the viewer knows the full document structure early
- render full pages and thumbnails either eagerly or lazily depending on the active mode; by default the same full page asset is reused for thumbnails while memory pressure allows it
- persist rendered page blobs in a second cache layer so later navigation and printing can usually reuse the same blob without re-rendering
- evict object URLs from RAM independently of the persisted blob caches when the active profile no longer wants everything resident
Workers are active again for raster images and TIFF whenever the configured backend allows it, and that routing decision is made per file/page asset rather than once for the entire run. PDF still uses the pdf.js rendering path. The default auto mode now keeps the old fast-feeling eager/full-image behavior through roughly the first 2000 pages, then falls back one-way to a more memory-efficient profile only for truly large or memory-stressed runs.
The same runtime system also fixes a serious page-consistency issue: the thumbnail selection is now tied to the actually displayed page until the viewer switches to an explicit loading overlay. This prevents the UI from showing “page X selected” while the large viewer still shows page Y.
A thin status bar above the toolbar shows which runtime profile is currently active: green for the fast eager path, yellow for the memory-efficient path, and red if the viewer has entered an error state.
The print pipeline lives mainly under:
src/utils/printCore.jssrc/utils/printDom.jssrc/utils/printTemplate.jssrc/utils/printParse.jssrc/components/DocumentToolbar/PrintRangeDialog.jsx
Key design points:
- printing uses a hidden iframe instead of popups
- current page printing prefers the renderer’s active canvas/image
- multi-page printing can use ordered full-size URLs and page metadata
- while the document is still loading, the print dialog intentionally stays in an active-page-only mode to avoid unstable page-range UI
- optional header overlays are injected into the print iframe DOM
For the logging server contract and reverse-proxy examples, see docs-src/log-servers.md.
Client code lives in src/logging/userLogger.js and records user print metadata such as reason / recipient, depending on runtime policy. Runtime default: disabled until explicitly enabled in odv.site.config.js.
Client code lives in src/logging/systemLogger.js. Runtime default: disabled until explicitly enabled in odv.site.config.js.
Optional ingestion servers live in:
server/system-log-server.jsserver/user-log-server.js
These are intentionally separate from the static frontend so deployments can choose whether to enable them.
For IIS-specific hosting, proxy setup, cache guidance, and ops checklists, see docs-src/deploy-ops.md.
OpenDocViewer/
├─ public/
│ ├─ odv.config.js
│ ├─ odv.site.config.sample.js
│ └─ demo assets and static files
├─ server/
│ ├─ system-log-server.js
│ └─ user-log-server.js
├─ src/
│ ├─ app/
│ │ ├─ AppBootstrap.jsx
│ │ ├─ OpenDocViewer.jsx
│ │ └─ bootConfig.js
│ ├─ components/
│ │ ├─ DocumentLoader/
│ │ ├─ DocumentToolbar/
│ │ ├─ DocumentViewer/
│ │ └─ shared render/support components
│ ├─ contexts/
│ ├─ integrations/
│ ├─ logging/
│ ├─ styles/
│ ├─ types/
│ ├─ utils/
│ └─ workers/
├─ docs-src/
├─ CONTRIBUTING.md
└─ README.md
The repository conventions are documented in CONTRIBUTING.md. The most relevant rules are:
*.jsxfor files that contain JSX*.jsfor hooks, utilities, integrations, workers, loggers, and other non-JSX modulesPascalCasefor React componentscamelCasefor non-component modules- update comments and JSDoc when changing module responsibilities
- If
npm cistalls or times out in CI- check
package-lock.jsonfor environment-specificresolvedURLs - verify
.npmrcand GitHub Actions usehttps://registry.npmjs.org
- check
- If the app starts without runtime config
- inspect
src/app/bootConfig.js - check that
odv.config.jsis reachable with a JavaScript content type
- inspect
- If embedded bootstrap fails
- inspect
src/integrations/bootstrapRuntime.js - confirm same-origin access for parent-window mode
- inspect
- If print output differs from the viewer
- check whether the current page is rendered via canvas or plain image
- inspect
src/utils/printCore.jsandsrc/utils/printDom.js
- If Firefox shows console warnings such as
Unable to check <input pattern=...>- treat them as browser-specific validation noise first
- OpenDocViewer is primarily validated for Chromium browsers (Edge/Chrome)
- If the performance overlay shows no heap numbers
- heap metrics rely on Chromium's
performance.memoryAPI - non-Chromium browsers such as Firefox will show
N/Ainstead of heap usage values
- heap metrics rely on Chromium's
MIT — see LICENSE.