---
title: "Embed Web SDK in a dashboard/app shell | Nutrient Web SDK"
canonical_url: "https://www.nutrient.io/guides/web/viewer/embed-in-dashboard-app-shell/"
md_url: "https://www.nutrient.io/guides/web/viewer/embed-in-dashboard-app-shell.md"
last_updated: "2026-06-16T11:26:40.312Z"
description: "Use this guide to implement a production-ready embedded viewer app shell with layout mounting, runtime assets setup, document switching, unload/reload lifecycle, and state restoration."
---

# 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

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:

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

```

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

```js

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.

## Related guides

- [Production runtime setup checklist](https://www.nutrient.io/guides/web/viewer/production-runtime-setup-checklist.md)

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

## Related pages

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

