> For the complete documentation index, see [llms.txt](https://docs.apryse.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.apryse.com/web/get-started/libraries-and-frameworks/nextjs.md).

# Build a Next.js PDF viewer with Apryse WebViewer SDK

Create a Next.js PDF viewer and editor with Apryse WebViewer. Learn how to integrate the SDK into your Next.js app and render PDFs in the viewer UI.

This guide shows how to build a Next.js PDF viewer using the Apryse [WebViewer SDK](/web/what-is-webviewer/overview.md). You'll learn how to integrate the SDK into a [Next.js](https://nextjs.org/docs) application to render, view, and interact with PDF documents using the WebViewer UI. This guide uses JavaScript examples. TypeScript projects will require minor adjustments.

You can also download a ready-to-use [GitHub sample](https://github.com/ApryseSDK/webviewer-samples/tree/main/webviewer-nextjs) to get started quickly, or explore the interactive [Showcase demo](https://showcase.apryse.com/) to see WebViewer's full capabilities in action.

## Prerequisites

This guide assumes basic familiarity with Next.js development. Before you start:

* Install [Node.js](https://nodejs.org/en/download) and npm. See the [Next.js documentation](https://nextjs.org/docs/app/getting-started/installation#system-requirements) for version details.
* Install [Visual Studio Code](https://code.visualstudio.com/Download) or another code editor to develop and debug your code.
* Get your Apryse trial key.

{% @apryse-license-key/apryse-license-key platform="WEB\_VIEWER" variant="full" %}

## 1. Create a Next.js project

In this section, you’ll create a new Next.js application using npm. This project provides the foundation for integrating Apryse WebViewer. If you already have a Next.js app, skip this and continue to [Install WebViewer](#2-install-webviewer).

1. In your terminal, go to the directory where you want to create the project.
2. Create a new `webviewer-nextjs` project using a minimal setup:

{% tabs %}
{% tab title="Shell" %}
{% code lineNumbers="true" %}

```shell
npx --yes create-next-app@latest webviewer-nextjs --js --no-eslint --no-react-compiler --no-tailwind --no-src-dir --app --yes
```

{% endcode %}
{% endtab %}
{% endtabs %}

{% hint style="info" %}
**Info**

Flags are used to skip prompts during project configuration. The setup uses JavaScript, skips ESLint configuration, disables React Compiler, excludes Tailwind CSS, omits the `src/` directory, enables App Router, and accepts other defaults. Select different options if preferred.
{% endhint %}

3. Navigate to your new Next.js project directory and install dependencies:

{% tabs %}
{% tab title="Shell" %}
{% code lineNumbers="true" %}

```shell
cd webviewer-nextjs
npm install
```

{% endcode %}
{% endtab %}
{% endtabs %}

## 2. Install WebViewer

Next, install the Apryse WebViewer SDK using npm. This command adds the [WebViewer package](https://www.npmjs.com/package/@pdftron/webviewer) to your project, allowing you to integrate the PDF viewer and editor into your Next.js application.

After navigating to your `webviewer-nextjs` project directory, run the following command to install WebViewer:

{% tabs %}
{% tab title="Shell" %}
{% code lineNumbers="true" %}

```shell
npm i @pdftron/webviewer
```

{% endcode %}
{% endtab %}
{% endtabs %}

## 3. Copy WebViewer assets

WebViewer needs access to its static assets at runtime, including WebAssembly modules, HTML, and CSS files. In a Next.js project, you must copy these assets into the `public` directory so they can be served correctly. For more, see [Copying WebViewer static assets](/web/get-started/copy-assets.md).

1. From your project root, create the `public/lib/webviewer` directory if it doesn't already exist:

{% tabs %}
{% tab title="Shell" %}
{% code lineNumbers="true" %}

```shell
npx --yes shx mkdir -p public/lib/webviewer
```

{% endcode %}
{% endtab %}
{% endtabs %}

2. Copy all WebViewer static assets from `node_modules/@pdftron/webviewer/public` into the new `public/lib/webviewer` directory:

{% tabs %}
{% tab title="Shell" %}
{% code lineNumbers="true" %}

```shell
npx --yes cpy-cli "node_modules/@pdftron/webviewer/public/**/*" public/lib/webviewer
```

{% endcode %}
{% endtab %}
{% endtabs %}

Your project should now include a similar structure:

{% code lineNumbers="true" %}

```
webviewer-nextjs/
├── .next/
├── app/
├── node_modules/
├── public/
│   ├── file.svg
│   └── lib/
│       └── webviewer/
│           ├── core/
│           └── ui/
└── ...
```

{% endcode %}

{% hint style="info" %}
**Info**

The `path` option used when initializing WebViewer must point to this directory (for example, `/lib/webviewer`). If the path is incorrect, WebViewer will fail to load.
{% endhint %}

## 4. Create the PDF viewer

In this section, you'll add WebViewer to your Next.js app by creating a component and initializing the viewer. This mounts the WebViewer UI and loads a document in your application.

1\. Create a `components` folder in your project's root:

{% tabs %}
{% tab title="Shell" %}
{% code lineNumbers="true" %}

```shell
npx --yes mkdirp components
```

{% endcode %}
{% endtab %}
{% endtabs %}

2. Create a `webviewer.js` file in the `components` folder:

{% tabs %}
{% tab title="Shell" %}
{% code lineNumbers="true" %}

```shell
npx --yes shx touch components/webviewer.js
```

{% endcode %}
{% endtab %}
{% endtabs %}

3. In Visual Studio Code, add this code to the `webviewer.js` file and save:

<pre class="language-js" data-line-numbers><code class="lang-js">// Runs only on the client
'use client';

// Import React hooks
import { useEffect, useRef } from 'react';

export default function WebViewer() {
  // Create a ref to hold div where WebViewer is mounted
  const viewer = useRef(null);

  // Run once after the component is mounted
  useEffect(() => {
    // Dynamically import WebViewer to avoid SSR issues
    import('@pdftron/webviewer').then((module) => {
      // Access the default export from the module
      const WebViewer = module.default;

      // Initialize WebViewer
      WebViewer(
        { 
          // Path to the WebViewer lib assets
          path: '/lib/webviewer',
          // Replace with your Apryse license key
          licenseKey: '<code class="expression">visitor.claims.wvKey || "YOUR_LICENSE_KEY"</code>',
          // Initial document to load into the viewer
          initialDoc: 'https://apryse.s3.amazonaws.com/public/files/samples/WebviewerDemoDoc.pdf',
        },
        // DOM element where WebViewer will be rendered
        viewer.current,
      ).then((instance) => {
        // Destructure the documentViewer from the Core API
        const { documentViewer } = instance.Core;

      // WebViewer APIs can now be used here
      // Example: documentViewer.addEventListener(...)
      });
    });

  // Empty dependency array ensures this runs once
  }, []);

  // Render the container that WebViewer mounts into
  return (
    &#x3C;div
      className='webviewer'
      ref={viewer}
      style={{ width: '100%', height: '100vh', margin: '0 auto' }}
    />
  );
}
</code></pre>

{% hint style="info" %}
**Info**

If you're signed in with an Apryse account, your license key is automatically prepopulated in all code snippets.
{% endhint %}

4. Replace the contents of the `app/page.js` file with the following and save:

{% tabs %}
{% tab title="JavaScript" %}
{% code title="app/page.js" lineNumbers="true" %}

```js
import WebViewer from '../components/webviewer';

export default function Page() {
  return (
    <div>
      <WebViewer />
    </div>
  );
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

## 5. Verify your output

You can now load and display a PDF document in the WebViewer UI. Run your Next.js application to launch WebViewer and see the PDF in your browser.

1. From your project directory, run the following command to start the application:

{% tabs %}
{% tab title="Shell" %}
{% code lineNumbers="true" %}

```shell
npm run dev
```

{% endcode %}
{% endtab %}
{% endtabs %}

A successful output looks similar to:

{% tabs %}
{% tab title="Shell" %}
{% code lineNumbers="true" %}

```shell
> webviewer-nextjs@0.1.0 dev
> next dev

▲ Next.js 16.2.7 (Turbopack)
- Local:         http://localhost:3000
✓ Ready in 413ms
```

{% endcode %}
{% endtab %}
{% endtabs %}

2. Open the localhost URL from your terminal to view the WebViewer UI and PDF document.

{% hint style="info" %}
**Info**

You may see a React hydration warning in the console when running the app locally. This is often caused by browser extensions modifying the page before React loads, not by your code. This warning is safe to ignore and doesn't affect WebViewer or PDF rendering. It will not appear in production.

You can suppress hydration warnings during development by overriding `console.error` — but we don’t recommend this unless you're absolutely sure hydration warnings are harmless.
{% endhint %}

## Get started video

In this 4-minute video, learn how to install and integrate the Apryse WebViewer SDK into a Next.js project using TypeScript. You’ll set up the PDF viewer, enable content editing, and explore the DOCX and Spreadsheet Editors.

{% embed url="<https://www.youtube.com/embed/0VoCnV0vyFE?si=EmZHj3gggFUjpXg6>" %}
Integrate WebViewer in a Next.js project with PDF editing, DOCX Editor, and Spreadsheet Editor.
{% endembed %}

## Troubleshooting

This section helps you resolve specific build errors when integrating WebViewer into a Next.js application.

#### The viewer renders as an empty box

If the page loads but the viewer area stays empty with no visible error, WebViewer can't load its static assets. Check the browser console for an error such as:

{% code lineNumbers="true" %}

```
Failed to load WebViewer Core script from <URL>
```

{% endcode %}

The `path` option doesn't point to your local copy of the WebViewer assets. Set it to the directory you created in the [Copy WebViewer assets](#3-copy-webviewer-assets) section (for this guide, it is the `/lib/webviewer`) directory). Don't point it at an external CDN or the npm package path.

#### Build fails with a Server Component error

If the build fails with an error such as:

{% code lineNumbers="true" %}

```
You're importing a module that depends on `useRef` into a React Server Component module.
This API is only available in Client Components.
To fix, mark the file (or its parent) with the `"use client"` directive.
```

{% endcode %}

WebViewer must run in a Client Component because it relies on browser APIs. Add the `'use client'` directive as the first line of the file that initializes WebViewer, as shown in the `webviewer.js` file example in the [Create the PDF viewer](#4-create-the-pdf-viewer) section.

#### Build fails with an ssr: false error

If the build fails with an error such as:

{% code lineNumbers="true" %}

```
`ssr: false` is not allowed with `next/dynamic` in Server Components. Please move it into a Client Component.
```

{% endcode %}

The WebViewer import was moved into `app/page.js` using `next/dynamic`. Instead, load WebViewer with the native `import('@pdftron/webviewer')` call inside the `useEffect` hook in `webviewer.js`, as shown in the [Create the PDF viewer](#4-create-the-pdf-viewer) section. The `useEffect` hook runs only in the browser, so no `ssr: false` workaround is needed.

## Next steps

<a href="/web/what-is-webviewer/usage.md" class="button primary">Usage</a><a href="/web/get-started/guides.md" class="button primary">Guides</a><a href="/web/get-started/samples.md" class="button primary">Samples</a><a href="https://sdk.apryse.com/api/web/index.html" class="button primary">API docs</a>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.apryse.com/web/get-started/libraries-and-frameworks/nextjs.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
