---
title: "Production runtime setup checklist | Nutrient Web SDK"
canonical_url: "https://www.nutrient.io/guides/web/viewer/production-runtime-setup-checklist/"
md_url: "https://www.nutrient.io/guides/web/viewer/production-runtime-setup-checklist.md"
last_updated: "2026-05-25T18:42:17.843Z"
description: "Use this operational checklist to get a production Web SDK app running reliably: assets mode, baseUrl/CDN, bundler wiring, license/env setup, runtime verification, and deployment checks."
---

# 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.

```js

await NutrientViewer.load({
  container: "#viewer",

  document: "/sample.pdf",
  baseUrl: "https://example.com/assets/"
});

```

This makes the SDK load files such as:

```text

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

```js

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 `licenseKey` from 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 call `instance.setSession(newJwt)`.

Below is an example of handling JWT expiration and renewal in Web SDK with Document Engine mode:

```js

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()`](https://www.nutrient.io/api/web/classes/NutrientViewer.Instance.html#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:

```text

viewer-runtime-vanilla/
├─ package.json
├─ index.html
├─ src/main.js
└─ public/
   ├─ sample.pdf
   └─ another.pdf

```

Install dependencies and define scripts:

```json

{
  "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:

```html

<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`:

```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:

```text

viewer-runtime-react/
├─ package.json
├─ index.html
├─ src/
│  ├─ main.jsx
│  └─ App.jsx
└─ public/
   └─ sample.pdf

```

Install React and viewer dependencies:

```json

{
  "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:

```html

<!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`:

```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`:

```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 `baseUrl` to 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](https://www.nutrient.io/guides/web/self-host-assets.md)

- [Open and display PDFs in the browser using JavaScript](https://www.nutrient.io/guides/web/open-a-document.md)

- [Embed Web SDK in a dashboard/app shell](https://www.nutrient.io/guides/web/viewer/embed-in-dashboard-app-shell.md)

- [Client authentication and session renewal](https://www.nutrient.io/guides/web/viewer/client-authentication.md)

- [PDF viewer troubleshooting](https://www.nutrient.io/guides/web/viewer/troubleshooting.md)
---

## Related pages

- [Embed Web SDK in a dashboard/app shell](/guides/web/viewer/embed-in-dashboard-app-shell.md)
- [Create custom annotation toggle button](/guides/web/viewer/custom-annotation-toggle.md)
- [Client authentication and session renewal](/guides/web/viewer/client-authentication.md)
- [JavaScript image viewer library](/guides/web/viewer/images.md)
- [Mobile responsive JavaScript PDF viewer](/guides/web/viewer/mobile-responsive.md)
- [JavaScript PDF viewer library](/guides/web/viewer.md)
- [JavaScript Support in our PDF viewer](/guides/web/features/javascript.md)
- [Enhance PDF viewing with linearized downloading](/guides/web/viewer/linearized-downloads.md)
- [Office document viewing in JavaScript](/guides/web/viewer/office-documents.md)
- [Page layout and scroll options in our JavaScript PDF viewer](/guides/web/customizing-the-interface/document-presentation-options.md)
- [JavaScript PDF viewer library](/guides/web/viewer/pdf.md)
- [Enable or disable permissions in our JavaScript viewer](/guides/web/features/document-permissions.md)
- [PDF document streaming in JavaScript](/guides/web/viewer/streaming.md)
- [macOS/Linux](/guides/web/viewer/troubleshooting.md)
- [Zoom options in our JavaScript PDF viewer](/guides/web/viewer/zooming.md)

