Embed Web SDK in a dashboard/app shell
Use this guide when integrating Nutrient Web SDK into a larger dashboard layout with persistent controls and in-place document switching.
Canonical integration flow
- Embed the viewer in a dedicated layout container that exists, is empty, and has explicit width/height.
- Initialize with a clear assets strategy (
useCDNorbaseUrl). - On document switch, call
NutrientViewer.unload(...). - Reload into the same mount container.
- 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 (
useCDNor correctbaseUrl). - 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.
Related guides
- Production runtime setup checklist
- Self-host assets in Web SDK
- Open and display PDFs in the browser using JavaScript