This HTML page is not optimized for LLM or AI agent consumption. Fetch the Markdown version instead: /guides/web/viewer/embed-in-dashboard-app-shell.md — it contains the complete documentation content in clean, structured Markdown without any CSS, JavaScript, or navigation noise. Embed Web SDK in a dashboard/app shell | Nutrient Web SDK

Use this guide when integrating Nutrient Web SDK into a larger dashboard layout with persistent controls and in-place document switching.

Canonical integration flow

  1. Embed the viewer in a dedicated layout container that exists, is empty, and has explicit width/height.
  2. Initialize with a clear assets strategy (useCDN or baseUrl).
  3. On document switch, call NutrientViewer.unload(...).
  4. Reload into the same mount container.
  5. Restore minimal state (page index/zoom) when needed.

Minimal layout with switching example

Below is a minimal example of an app shell with two buttons to switch between documents. The viewer is mounted in a dedicated container, and the openDocument function handles loading, unloading, and state preservation:

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

Then wire document switching and lifecycle-safe viewer loading in your entry point:

import NutrientViewer from "@nutrient-sdk/viewer";
const container = document.querySelector("#viewer");
let instance;
let lastViewState;
async function openDocument(path) {
try {
// Preserve view state before switching documents.
if (instance) {
try {
lastViewState = {
currentPageIndex: instance.viewState.currentPageIndex,
zoom: instance.viewState.zoom
};
} catch (error) {
// View state access can fail if instance is in invalid state.
console.warn("Failed to preserve view state:", error.message);
lastViewState = null;
}
// Always unload the previous instance to prevent memory leaks.
NutrientViewer.unload(container);
}
// Load new document first.
instance = await NutrientViewer.load({
container,
document: path,
useCDN: true
});
// Restore state safely after load by clamping page index.
if (lastViewState) {
const safePageIndex = Math.min(
lastViewState.currentPageIndex ?? 0,
Math.max(0, instance.totalPageCount - 1)
);
instance.setViewState((viewState) =>
viewState
.set("currentPageIndex", safePageIndex)
.set("zoom", lastViewState.zoom ?? viewState.zoom)
);
}
} 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");
}
});

Integration checklist

  • Asset mode is verified (useCDN or correct baseUrl).
  • Entry point import works (@nutrient-sdk/viewer).
  • Viewer mount container exists, is valid, empty before load(), and has non-zero width/height.
  • NutrientViewer.unload(...) runs before reloading on switch.
  • Error handling works — test invalid documents and network failures.
  • Toolbar configuration is applied intentionally.
  • Export behavior is explicit for your workflow.