Production runtime setup checklist
Use this page as the canonical runtime path for shipping a stable Web SDK deployment.
Assets mode and serving
Choose one runtime asset mode:
- CDN mode (
useCDN: true) for fastest setup. - Self-hosted mode (
baseUrl) for controlled/compliant/offline deployments.
For self-hosting, serve the entire assets bundle with its original folder structure, including the nutrient-viewer-lib/ directory. Set baseUrl to the URL above that folder.
await NutrientViewer.load({ container: "#viewer", document: "/sample.pdf", baseUrl: "https://example.com/assets/"});This makes the SDK load files such as:
https://example.com/assets/nutrient-viewer-lib/...Bundler with entry point wiring
Confirm your app entry point imports and initializes the viewer only after the mount container meets all of the following conditions:
- Exists
- Resolves to a valid element or CSS selector
- Is empty
- Has non-zero width and height
import NutrientViewer from "@nutrient-sdk/viewer";
await NutrientViewer.load({ container: "#viewer", document: "/sample.pdf", useCDN: true});License and environment configuration
Make sure to set up your license key and any other environment-specific configuration securely:
- Inject
licenseKeyfrom environment/configuration in production. - Don’t hardcode secrets in client code.
- For server-backed (Document Engine-backed mode) flows, use short-lived JSON Web Tokens (JWTs) and refresh them with
onAuthFailed. Then callinstance.setSession(newJwt).
Below is an example of handling JWT expiration and renewal in Web SDK with Document Engine mode:
let instance;
instance = await NutrientViewer.load({ container: "#viewer", documentId: "doc-id", serverUrl: "https://your-document-engine.example.com/", authPayload: { jwt: initialJwt }, onAuthFailed: async () => { const newJwt = await fetchNewJwtFromBackend(); instance.setSession(newJwt); }});Runtime verification steps
Before deploying, verify the following runtime behaviors in a staging environment that closely matches production:
- Viewer mounts successfully in intended container.
- Runtime assets load from expected host/path.
- Document open, page navigation, and toolbar actions work.
- Error handling works — test invalid documents, network failures.
- Document switching unload/reload works without leaks.
- Export works through the instance API (
await instance.exportPDF()), including any selected export options.
Minimal runnable starter templates
Below are minimal runnable templates for both vanilla and React setups using Vite. You can clone these and modify as needed for your production app.
Vanilla (Vite)
Set up a vanilla JavaScript app with Vite and the Web SDK:
viewer-runtime-vanilla/├─ package.json├─ index.html├─ src/main.js└─ public/ ├─ sample.pdf └─ another.pdfInstall dependencies and define scripts:
{ "name": "viewer-runtime-vanilla", "private": true, "scripts": { "dev": "vite", "build": "vite build", "preview": "vite preview" }, "dependencies": { "@nutrient-sdk/viewer": "latest" }, "devDependencies": { "vite": "latest" }}Create the HTML mount container and document-switch controls:
<div> <button id="docA">Open A</button> <button id="docB">Open B</button></div><div id="viewer" style="height: 100vh"></div><script type="module" src="/src/main.js"></script>Initialize the viewer and wire lifecycle-safe document switching in src/main.js:
import NutrientViewer from "@nutrient-sdk/viewer";
const container = document.querySelector("#viewer");let instance;
async function openDocument(path) { try { // Always unload previous instance to prevent memory leaks. if (instance) NutrientViewer.unload(container);
// Load new document — CDN mode for fastest setup. instance = await NutrientViewer.load({ container, document: path, useCDN: true }); } catch (error) { // Log with context for easier debugging. console.error(`Failed to load document ${path}:`, error.message);
// Could show user-friendly error in production. throw error; }}
// Initial document load with error handling.try { await openDocument("/sample.pdf");} catch (error) { console.error("Failed to load initial document:", error.message);}
document.querySelector("#docA")?.addEventListener("click", async () => { try { await openDocument("/sample.pdf"); } catch (error) { // Handle error appropriately in your app. console.error("Document A failed to load"); }});
document.querySelector("#docB")?.addEventListener("click", async () => { try { await openDocument("/another.pdf"); } catch (error) { // Handle error appropriately in your app. console.error("Document B failed to load"); }});React (Vite)
Set up a React app with Vite and the Web SDK:
viewer-runtime-react/├─ package.json├─ index.html├─ src/│ ├─ main.jsx│ └─ App.jsx└─ public/ └─ sample.pdfInstall React and viewer dependencies:
{ "name": "viewer-runtime-react", "private": true, "scripts": { "dev": "vite", "build": "vite build", "preview": "vite preview" }, "dependencies": { "@nutrient-sdk/viewer": "latest", "react": "latest", "react-dom": "latest" }, "devDependencies": { "@vitejs/plugin-react": "latest", "vite": "latest" }}Create the Vite HTML entry file:
<!doctype html><html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Nutrient Viewer React</title> </head> <body> <div id="root"></div> <script type="module" src="/src/main.jsx"></script> </body></html>Mount the React app in src/main.jsx:
import React from "react";import ReactDOM from "react-dom/client";import App from "./App";
ReactDOM.createRoot(document.getElementById("root")).render( <React.StrictMode> <App /> </React.StrictMode>);Load and unload the viewer in src/App.jsx:
import { useEffect, useRef } from "react";import NutrientViewer from "@nutrient-sdk/viewer";
export default function App() { const containerRef = useRef(null);
useEffect(() => { const container = containerRef.current; if (!container) return;
let cancelled = false;
async function loadViewer() { try { // In React.StrictMode, effects run twice in development. // Unload first to avoid mounting into a container with an existing instance. NutrientViewer.unload(container);
await NutrientViewer.load({ container, document: "/sample.pdf", useCDN: true }); } catch (error) { if (cancelled) return; console.error("Failed to load viewer:", error); } }
loadViewer();
return () => { cancelled = true; NutrientViewer.unload(container); }; }, []);
return <div ref={containerRef} style={{ height: "100vh" }} />;}Recommended asset strategy
Use the following guidance to choose the asset delivery mode that best fits your deployment and compliance requirements.
- CDN mode — Set
useCDN: true. - Self-hosted mode — Extract assets into public root and set
baseUrlto that served path.
Runtime troubleshooting matrix
Use this matrix to map runtime symptoms to likely causes and the fastest corrective action.
| Symptom | Likely cause | Fix |
|---|---|---|
404 on .wasm/worker/font assets | Incorrect baseUrl or incomplete asset extraction | Verify baseUrl, extract full archive, preserve directory structure. |
load() fails with container error | Mount element missing at init time | Ensure container exists before calling NutrientViewer.load(...). |
| Document fails to load | Wrong URL/CORS/unsupported source | Check network/CORS headers and document path/format. |
| License error at startup | Invalid/expired/domain-mismatched key | Validate license source, environment injection, and domain constraints. |
| Switching documents causes leaks or unstable UI | Previous instance not unloaded | Call NutrientViewer.unload(container) before reloading. |
| Toolbar actions disappear after switch | Toolbar configuration not reapplied on reload | Reinitialize toolbar configuration on each load(). |
Deployment scenarios
Below are some common production deployment considerations.
Single instance vs. multiple viewers
- Prefer one active instance per mount container.
- If rendering multiple viewers concurrently, budget memory carefully and unload inactive viewers.
Preview/staging deployment behavior
- Validate asset paths in the deployed URL base (especially subpath deployments).
- Smoke-test load, switch, and export in staging before production promotion.
Token/license rotation and configuration hygiene
- Keep license/runtime configuration environment-driven.
- Rotate server-backed session tokens using supported runtime hooks.
- Avoid committing environment-specific runtime values into source.
Related guides
- Self-host assets in Web SDK
- Open and display PDFs in the browser using JavaScript
- Embed Web SDK in a dashboard/app shell
- Client authentication and session renewal
- PDF viewer troubleshooting