PDF web server integration with cloud PDF viewer
To load a document in Nutrient Web SDK from Document Engine, call NutrientViewer.load() with a configuration object:
NutrientViewer.load({ container: "#nutrient-container", documentId: "<document_id>", authPayload: { jwt: "<jwt>" }, instant: true, serverUrl: "https://<your_document_engine_instance>/"});The configuration options are:
container— A CSS selector (e.g."#nutrient-container") or DOM element where you mount the viewer. The element must exist in the DOM before callingload().documentId— The identifier of an existing document on Document Engine. See the API reference for information on how to upload documents.authPayload— An object containing the JWT for authentication. See the guide on generating a JWT for details.instant— Whether to enable Nutrient Instant real-time collaboration.serverUrl— The URL of your Document Engine instance. See theserverUrlconfiguration section below.
Understanding serverUrl
The serverUrl configuration tells the Web SDK where to find Document Engine.
The Web SDK can automatically infer the Document Engine URL from the base URL of the nutrient-viewer.js script tag. However, you must set serverUrl if you serve the script from a different location than Document Engine, such as a CDN or your bundled application.
| Scenario | serverUrl required? |
|---|---|
| Script served from Document Engine | No — automatically inferred |
| Script served from CDN | Yes |
| Script bundled in your application | Yes |
Example — Serving from Document Engine (no serverUrl needed):
<script src="https://document-engine.example.com/nutrient-viewer.js"></script><script> NutrientViewer.load({ container: "#viewer", documentId: "abc123", authPayload: { jwt: "..." } // `serverUrl` is inferred from the script location. });</script>Example — Serving from CDN (serverUrl required):
<script src="https://cdn.cloud.nutrient.io/pspdfkit-web@1.22.0/nutrient-viewer.js"></script><script> NutrientViewer.load({ container: "#viewer", documentId: "abc123", authPayload: { jwt: "..." }, serverUrl: "https://document-engine.example.com/" });</script>How to serve the Web SDK
This section outlines various options for serving Nutrient Web SDK for use as a Document Engine client in your web applications.
These options describe how to deliver the Web SDK JavaScript bundle. They don’t describe the service that stores or processes documents. DWS Viewer API uses the same Web SDK bundle with DWS session authentication. It can load the bundle from the CDN or from your own application assets. For a complete DWS integration, follow the DWS Viewer API getting started guide. For a standalone Web SDK setup with a publishable key, follow the Web SDK quick start guide.
The Serving from Document Engine option below applies only to self-hosted Document Engine. DWS Viewer API doesn’t proxy the Web SDK bundle through a Document Engine instance.
Serving from Document Engine
To load Nutrient Web SDK from Document Engine, load the main nutrient-viewer.js script like so:
<script src="https://<document_engine_url>/nutrient-viewer.js"></script>Document Engine proxies these requests to our Web SDK CDN served at https://cdn.cloud.nutrient.io.
However, this approach has limitations. We recommend the other options if:
- You can’t or don’t want to provide access to our CDN, as Document Engine can’t proxy the files without it.
- You wish to use a newer Web SDK version. Document Engine serves the version current at its own release, which might be older than the latest Web SDK release.
Serving from CDN
We maintain a CDN with the Web SDK bundle at https://cdn.cloud.nutrient.io. Document Engine uses it (refer to the previous section), and customers can also use it directly.
To load Nutrient Web SDK from the CDN, load the main nutrient-viewer.js script like so:
<script src="https://cdn.cloud.nutrient.io/pspdfkit-web@1.22.0/nutrient-viewer.js"></script>Serving manually
Finally, you can always serve the Web SDK manually within your applications. This has the benefit of working even offline or generally when you don’t want to or can’t provide access to our CDN.
Nutrient Web SDK ships as an npm package(opens in a new tab) that’s usually installed via a package manager:
yarn add @nutrient-sdk/viewernpm install --save @nutrient-sdk/viewerOnce it’s installed, you need to serve the contents of /node_modules/@nutrient-sdk/viewer/dist to your frontend. Multiple options for serving it depend on your environment.
The simplest option is to add it to the static assets of your application. You can then refer to it the same as you refer to any other script:
<script src="https://<your_app_url>/static/nutrient-web/nutrient-viewer.js"></script>To copy the Web SDK files to your project’s static directory, you can make use of npm prepare script(opens in a new tab):
"scripts": { ... "prepare": "mkdir -p ./<your_projects_static_directory>/nutrient-web && cp -R ./node_modules/@nutrient-sdk/viewer/dist/ ./public/nutrient-web/"},Make sure to replace the <your_projects_static_directory> placeholder with the actual directory for static files in your project.
Framework integration
This section shows how to integrate the Web SDK with Document Engine in popular JavaScript frameworks.
React
This component loads and unloads the viewer as the document, JWT, or server URL change:
import { useEffect, useRef } from "react";
function PDFViewer({ documentId, jwt, serverUrl }) { const containerRef = useRef(null);
useEffect(() => { const container = containerRef.current; let instance;
(async () => { const NutrientViewer = (await import("@nutrient-sdk/viewer")).default;
instance = await NutrientViewer.load({ container, documentId, authPayload: { jwt }, serverUrl, }); })();
return () => instance?.unload(); }, [documentId, jwt, serverUrl]);
return <div ref={containerRef} style={{ height: "100vh" }} />;}Vue 3
This component loads the viewer on mount and unloads it when the component is destroyed:
<template> <div ref="container" style="height: 100vh"></div></template>
<script setup>import { ref, onMounted, onUnmounted } from "vue";
const props = defineProps(["documentId", "jwt", "serverUrl"]);const container = ref(null);let instance = null;
onMounted(async () => { const NutrientViewer = (await import("@nutrient-sdk/viewer")).default;
instance = await NutrientViewer.load({ container: container.value, documentId: props.documentId, authPayload: { jwt: props.jwt }, serverUrl: props.serverUrl, });});
onUnmounted(() => instance?.unload());</script>Angular
This component loads the viewer after the view initializes and unloads it on destroy:
import { Component, ElementRef, ViewChild, AfterViewInit, OnDestroy, Input,} from "@angular/core";
@Component({ selector: "pdf-viewer", template: '<div #container style="height: 100vh"></div>',})export class PdfViewerComponent implements AfterViewInit, OnDestroy { @ViewChild("container") container!: ElementRef; @Input() documentId!: string; @Input() jwt!: string; @Input() serverUrl!: string;
private instance: any;
async ngAfterViewInit() { const NutrientViewer = (await import("@nutrient-sdk/viewer")).default;
// Note: `ViewChild` is only available after view initialization. this.instance = await NutrientViewer.load({ container: this.container.nativeElement, documentId: this.documentId, authPayload: { jwt: this.jwt }, serverUrl: this.serverUrl, }); }
ngOnDestroy() { this.instance?.unload(); }}Container timing: In frameworks like Angular, the container element may not be available immediately. Ensure you wait for the view to initialize (e.g. use ngAfterViewInit in Angular or useEffect/onMounted in React/Vue) before calling NutrientViewer.load().
Cross-origin configuration (CORS)
When your web application runs on a different domain than Document Engine, Cross-Origin Resource Sharing (CORS) must be configured via the JWT allowed_origins claim.
Configuring allowed origins
Document Engine uses the allowed_origins claim in the JWT to determine which origins are permitted. When generating a JWT, include the allowed_origins claim with your frontend domain(s):
{ "document_id": "abc123", "permissions": ["read-document"], "allowed_origins": ["https://your-app.com", "https://staging.your-app.com"]}Set allowed_origins to "any" to allow requests from any origin (not recommended for production).
Common CORS errors
If you see an error like:
Access to fetch at
https://document-engine.example.com/i/d/5/authfrom originhttps://your-app.comhas been blocked by CORS policy
Check that:
- The JWT includes an
allowed_originsclaim with your frontend domain. - The protocol matches exactly (
httpvs.https). - The port is included if using a non-standard port.
Refer to the JWT authentication guide for details on configuring the allowed_origins claim.
License and domain configuration
Document Engine validates that requests originate from licensed domains.
Origin validation errors
If you see an error like:
PSPDFKit Document Engine is not licensed for use from the origin
http://localhost:8080
This means the requesting origin isn’t included in your license configuration.
To resolve:
- Development — Use a development license that includes
localhost. - Production — Contact Support(opens in a new tab) to add your production domains to your license.
Refer to the domain configuration guide for more details.
Differences from standalone mode
When using Document Engine instead of standalone Web SDK, some behaviors differ. These are outlined in the table below.
| Feature | Standalone | Document Engine |
|---|---|---|
| Document source | URL, ArrayBuffer, or file | documentId on server |
| Annotation storage | Local or custom backend | Server-managed |
instantJSON parameter | Loads annotations from JSON | Ignored — annotations managed by server |
autoSaveMode | Controls local saving | Controls syncing to Document Engine |
| Document streaming | Not available | Enabled by default |
Performance considerations
To improve load times within your application, we recommend using preload and prefetch in adequate scenarios. Preload nutrient-viewer.js on sites where you plan to display PDF content. Prefetch it on sites where no PDF content is visible.
The preload attribute of the <link> tag causes your browser to start downloading the resource earlier in the page lifecycle. This improves the overall feel of your site. The prefetch attribute fetches resources you predict a user is likely to request. See the HTML specification(opens in a new tab) for more information.
The implementation of these will depend on how you’re importing nutrient-viewer.js on your site/application. If your service is a regular website using vanilla JavaScript, add <link rel="prefetch" href="path/to/nutrient-viewer.js"> or <link rel="preload" href="path/to/nutrient-viewer.js">. Place it within the <head> of the pages in question.
For websites and applications with modern frameworks that use webpack, use the /* webpackPrefetch: true */ or /* webpackPreload: true */ comments within your import(). Webpack will use this information to generate the appropriate <link rel="prefetch" ...> or <link rel="preload" ...> tags for you. See the webpack documentation(opens in a new tab) for more information.
Version compatibility
Document Engine and Web SDK versions are generally compatible in server-backed mode, but some Web SDK features require a minimum Document Engine version. The table below lists known requirements for those feature and version combinations. It isn’t a complete compatibility matrix for current releases, so check the release notes for the specific versions you plan to deploy.
| Web SDK version | Incompatible Document Engine version | Affected functionality |
|---|---|---|
| 1.13–1.18 | Earlier than 1.12.0 | instance.exportPDF(). These Document Engine versions require legacy query-parameter authentication for /pdf, which these Web SDK releases don’t send. |
| 1.15 or later | Earlier than 1.16.0 | SearchType.WORD_BASED in server-backed mode. |
| 1.16 or later | Earlier than 1.16.0 | Removing password protection during server-backed PDF exports. |
| 1.16 or later | Earlier than 1.16.0 | Removing annotation notes after an XFDF roundtrip. |
| 1.19 or later | Earlier than 1.18.0 | Document-defined annotation tab order in server-backed mode. Web SDK doesn’t request the unsupported endpoint and retains fallback tab ordering. instance.getPageTabOrder() reports that the Document Engine version doesn’t provide the document-defined order. |
When serving Web SDK from Document Engine, it provides the Web SDK version that shipped with that Document Engine release. When serving from the Nutrient CDN or bundling Web SDK manually, check the combinations above.
Troubleshooting
This section covers common issues you may encounter when integrating Nutrient Web SDK with Document Engine, along with their solutions. For issues not listed here, refer to the related guides below or contact Support(opens in a new tab).
Common errors
| Error | Cause | Solution |
|---|---|---|
container must be a valid element | Element doesn’t exist when load() is called | Wait for DOM to be ready; use framework lifecycle hooks |
Not licensed for origin | Domain not in license | Add domain to license; see domain configuration |
| CORS errors | Cross-origin not configured | Add the allowed_origins claim to JWT |
| Connection timeout | Network or timeout configuration | See the 504 response guide |
| Authentication failed | Invalid or expired JWT | Regenerate JWT and verify claims (including exp). For expired tokens in long-running sessions, use runtime refresh with onAuthFailed + setSession |
To renew JWTs without recreating the viewer, refer to the web client authentication and session renewal guide.