PDF web server integration with cloud PDF viewer
To load a document in Nutrient Web SDK from Document Engine, pass the document’s ID, the JSON Web Token (JWT) for authentication, and information as to whether or not Instant real-time collaboration should be enabled in the configuration object passed to NutrientViewer.load():
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 the viewer will be mounted. 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 Nutrient Instant real-time collaboration should be enabled.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, if you serve the script from a different location than Document Engine (such as from CDN or bundled in your application), you must explicitly set serverUrl.
| 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.pspdfkit.com/pspdfkit-web@1.12.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.
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 this requests to our Web SDK CDN served at https://cdn.cloud.pspdfkit.com.
However, there are limitations to this approach, and it’s recommended to use the other options if:
- You can’t or don’t want to provide access to our CDN, as Document Engine won’t be able to proxy the files.
- You wish to use a newer version of the Web SDK, since Document Engine serves the latest version of the Web SDK at the time of the Document Engine release, which might be older than the latest Web release.
Serving from CDN
We maintain a CDN with the Web SDK bundle at https://cdn.cloud.pspdfkit.com. It’s used by Document Engine (refer to the previous section), and it’s also available to be used by our customers.
To load Nutrient Web SDK from the CDN, load the main nutrient-viewer.js script like so:
<script src="https://cdn.cloud.pspdfkit.com/pspdfkit-web@1.12.0/nutrient-viewer.js"></script>Serving manually
Finally, you can always serve the Web SDK manually within your applications. This has a 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 serve the contents of /node_modules/@nutrient-sdk/viewer/dist to your frontend. There are multiple options for serving it that 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
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
<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
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, 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 (
httpvshttps). - 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 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: preloading nutrient-viewer.js for sites on which you wish to display PDF content using Nutrient, and prefetching on sites on which no PDF content is visible.
The preload attribute of the <link> tag causes your browser to start downloading the resource of the <link> tag earlier in the lifecycle of your page, which will improve the overall feel of your site, while the prefetch attribute is used to fetch the 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, you can add <link rel="prefetch" href="path/to/nutrient-viewer.js"> or <link rel="preload" href="path/to/nutrient-viewer.js"> within the <head> of the pages in question.
For websites and applications with modern frameworks that use webpack for bundling, this can be done using 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
For best results, use compatible versions of Document Engine and Web SDK:
- When serving Web SDK from Document Engine, versions are automatically matched.
- When serving from CDN or bundling manually, ensure your Web SDK version is compatible with your Document Engine version.
- Check the requirements guide for minimum version compatibility.
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.
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; verify exp claim |