---
title: "Add PDF functionality with PWA"
canonical_url: "https://www.nutrient.io/sdk/web/getting-started/other-frameworks/pwa/"
md_url: "https://www.nutrient.io/sdk/web/getting-started/other-frameworks/pwa.md"
last_updated: "2026-06-08T09:14:14.525Z"
description: "Learn how to integrate Nutrient Web SDK into your PWA application. Step-by-step guide for adding PDF viewing, editing, and annotation features."
---

# Add PDF functionality with PWA

Nutrient Web SDK is a JavaScript PDF library for viewing, annotating, and editing PDFs directly in the browser. Use it to add PDF capabilities to any web app.

This guide assumes you’ve already configured Nutrient Web SDK for your specific framework. If you haven’t, select the guide for your framework and configure Nutrient Web SDK. Then return to this guide.

Nutrient’s PWA must be served by a web server over HTTPS, so make sure your server is configured to serve pages in HTTPS. In development, however, Nutrient doesn’t need HTTPS since browsers treat `localhost` as a secure origin.

**Test without installing**

You can test the SDK capabilities in our playground.

[Read more](https://nutrient.io/demo/sandbox)

**Jump to example**

Prefer to jump straight to code? View the example repo on GitHub.

[Read more](https://github.com/PSPDFKit/nutrient-web-examples/tree/main/examples/pwa)

## Project setup

#### Note: Creating a new project (optional)

For PWAs, first create a project using your preferred framework (for example, React, Vue, or Angular), integrate Nutrient Web SDK using that framework’s guide, and then continue with the PWA-specific steps below.

## Opening a PDF

To read a PDF, you’ll need a helper to read the selected file from disk using the FileReader API:

```js

//./src/app.js

function registerFilePicker(element, callback) {
  function handler(event) {
    if (event.target.files.length == 0) {
      event.target.value = null;
      return;
    }
    var pdfFile = event.target.files[0];
    if (pdfFile.type!== "application/pdf") {
      alert("Invalid file type, please load a PDF.");
      return;
    }

    var reader = new FileReader();
    reader.addEventListener("load", function (event) {
      var pdf = event.target.result;
      callback(pdf, pdfFile);
    });
    reader.addEventListener("error", function (error) {
      alert(error.message);
    });
    reader.readAsArrayBuffer(pdfFile);
    event.target.value = null;
  }

  element.addEventListener("change", handler);

  return function () {
    element.removeEventListener("change", handler);
  };
}

```

`callback` is a function that gets the `pdf` in the `ArrayBuffer` format so that you can load it directly with Nutrient Web SDK. It also gets the selected `File` object.

Once you have this helper, you can add the code to initialize Nutrient Web SDK:

```js

// src/app.js
var nutrientViewerInstance = null;
var filePicker = document.querySelector('input[type="file"]');

registerFilePicker(filePicker, function (pdf, fileInfo) {
  if (nutrientViewerInstance) {
    NutrientViewer.unload(nutrientViewerInstance);
  }

  NutrientViewer.load({
    document: pdf,
    container: ".Nutrient-container",
    // See https://nutrient.io/api/web/PSPDFKit.Configuration.html#enableServiceWorkerSupport

    enableServiceWorkerSupport: true,
  }).then(function (instance) {
    nutrientViewerInstance = instance;
  });
});

```

Nutrient’s [advanced PWA example](https://github.com/PSPDFKit/nutrient-web-examples/tree/main/examples/pwa) enables you to load PDF files from a remote server, and it uses IndexedDB to cache them locally for offline use. It also uses the History API to easily load files using URL.

### Optimizing load performance

If your app doesn't open a PDF immediately — for example, the user navigates to a viewer on a different page — call [`NutrientViewer.preloadWorker()`](https://www.nutrient.io/api/web/functions/NutrientViewer.preloadWorker.html) early in your app to fetch and compile the WebAssembly artifacts in the background. When `NutrientViewer.load()` runs later, it can start rendering without waiting for them.

```js

// Call early — for example, on app init or after login.
NutrientViewer.preloadWorker();

```

Refer to the [performance best practices](https://www.nutrient.io/guides/web/best-practices/performance.md) guide for more optimization techniques.




## Adding caching and offline capabilities with service workers

One of the most important features of PWAs is the ability to load fast and to work on slow network conditions, or even offline. To achieve this, you can use a service worker to cache the application shell and, when available, use the network only to fetch necessary data.

The service worker is a script you register in the application shell. It runs in the background, separate from a webpage. It can intercept and handle network requests, enabling you to cache responses programmatically.

In case you’re not familiar with service workers, we highly recommend you read [this excellent introductory blog post](https://developer.chrome.com/docs/workbox/service-worker-overview/).

**Steps:**

1. Create the `serviceworker.js` file with the following contents:

   ```js

   //./serviceWorker.js

   console.log("Hello from the Service Worker");
   ```

2. Then, register it in your application shell:

   ```html <!-- Lines inserted: [{range: "11-17"}] -->

   <!--./src/index.html -->

   <!DOCTYPE html>
   <html>
     <head>
       <meta charset="UTF-8" />
       <meta
         name="viewport"
         content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
       />
       <title>Nutrient PWA</title>

       <script>
         if ("serviceWorker" in navigator) {
           window.addEventListener("load", function () {
             navigator.serviceWorker.register("./serviceWorker.js");
           });
         }
       </script>
     </head>
   </html>
   ```

   This uses feature detection to determine whether service workers are supported and to register your `serviceWorker.js` when the feature is available.

3. You can now start your application and try it in a web browser. In the Application tab of Chrome DevTools, you’ll see that your service worker has been registered and is active.!["Screenshot of the Application > Service Workers tab in Chrome"](@/assets/getting-started/sdk/web/pwa/serviceWorker.png)

   For local development, it’s a good idea to check the **Update on reload** option so that the service worker is updated every time the page reloads. You can clear the service worker storage any time from this panel using the **Clear storage** view.

## Use Workbox for production service workers

The Service Worker API is low level, flexible, and powerful. Because of this, it usually requires some boilerplate code to do common tasks like activate the service worker, intercept requests and cache responses, clear the cache, and precache files.

To simplify those tasks, Google developed [Workbox](https://developer.chrome.com/docs/workbox/), an open source library that abstracts away all the complexity and makes building PWAs easy. Before deploying to production, we recommend using Workbox to manage your service workers.

Workbox can help with doing more than precaching, and it enables you to configure how each resource should be cached and how routes should be handled.

Complex applications will likely need to use the network to fetch data before they can render content in the app shell. In those cases, it’s important to choose the correct [caching strategy](https://jakearchibald.com/2014/offline-cookbook/) for your data.

Refer to the [Workbox website](https://developer.chrome.com/docs/workbox/the-ways-of-workbox/) to learn more about how to handle advanced use cases.

## Final touches

Now that your app has offline capabilities, you only need to add a web app manifest to make the application recognizable by the web browser and to describe how the app should behave when installed on users’ devices.

**Steps:**

1. The web app manifest is a file whose name is `manifest.json`. It contains metadata like the name of the app, the paths to icons and their sizes, the start URL, and the theme color. Now you’ll create a basic one for your PWA:

   ```sh

   touch./src/manifest.json
   ```

   ```json

   {
     "name": "Nutrient PWA",
     "short_name": "Nutrient",
     "icons": [
       {
         "src": "images/icons/icon-192x192.png",
         "sizes": "192x192",
         "type": "image/png"
       },
       {
         "src": "images/icons/icon-512x512.png",
         "sizes": "512x512",
         "type": "image/png"
       }
     ],
     "start_url": "./index.html",
     "display": "standalone",
     "background_color": "#0089AA",

     "theme_color": "#0089AA"

   }
   ```

2. Finally, you need to register the manifest in the app pages — in your case, `index.html`:

   ```html

   <link rel="manifest" href="./manifest.json" />
   ```

3. You can verify the manifest in the **Application** tab of Chrome DevTools:!["Screenshot of the App Manifest"](@/assets/getting-started/sdk/web/pwa/manifest.png)

The web app manifest also makes it possible to display an App Install Banner or an Add to Home Screen dialog. Follow the [guidelines from Google](https://web.dev/articles/customize-install) to learn how to create and display one.

## A note about progressive enhancement

By definition, PWAs are progressive, meaning they’re inclusive and they rely heavily on progressive enhancement. When building a PWA, it’s good to always keep this in mind and provide a basic experience for every user of an application.

Richer features should be built on top of an always-working barebones implementation. We highly recommend using feature detection to provide progressive enhancement so that applications won’t break in older browsers that don’t support a specific feature.

## Limitations

Keep browser-specific storage constraints in mind when designing offline behavior and cache retention policies.

### Disk quota

Web browsers define quotas either per origin (Chrome and Opera) or per API (for example, IndexedDB, service workers). When storing files via web APIs, it’s a good idea to keep this in mind and ideally monitor the disk quota status to avoid failures. Apps can check how much quota they’re using with the Quota Management API.

## Troubleshooting

**Example**

View the example repo on GitHub.

[Read more](https://github.com/PSPDFKit/nutrient-web-examples/tree/main/examples/pwa)

**Facing issues?**

Visit the troubleshooting guide for solutions to some common errors.

[Read more](https://www.nutrient.io/guides/web/troubleshooting/common-issues.md)
---

## Related pages

- [Add PDF functionality with PHP](/sdk/web/getting-started/other-frameworks/php.md)
- [Add PDF functionality with JavaScript + Vite](/sdk/web/getting-started/other-frameworks/javascript.md)
- [Add PDF functionality with Flutter](/sdk/web/getting-started/other-frameworks/flutter.md)
- [Add PDF functionality with Nuxt](/sdk/web/getting-started/other-frameworks/nuxt.md)
- [Add PDF functionality with ASP.NET](/sdk/web/getting-started/other-frameworks/aspnet.md)
- [Add PDF functionality with Laravel](/sdk/web/getting-started/other-frameworks/laravel.md)
- [Add PDF functionality with Svelte](/sdk/web/getting-started/other-frameworks/svelte.md)
- [Add PDF functionality with jQuery](/sdk/web/getting-started/other-frameworks/jquery.md)
- [Add PDF functionality with Electron](/sdk/web/getting-started/other-frameworks/electron.md)
- [Add PDF functionality with Blazor](/sdk/web/getting-started/other-frameworks/blazor.md)
- [Add PDF functionality with Angular](/sdk/web/getting-started/other-frameworks/angular.md)
- [Add PDF viewing and editing to Vue applications](/sdk/web/getting-started/other-frameworks/vue.md)

