Next.js
Use React PDF Viewer in the Next.js App Router, including Server Components and a self-hosted PDF.js worker.
Before you start
- Tested with Next.js 16.3 (App Router), React 19.3,
pdfjs-dist6.3 and TypeScript 6.0. The consumer smoke test builds this setup against the packed package on every CI run and checks that the viewer renders in the prerendered HTML. - Install the package first: see Installation.
Setup
1. Import the stylesheet in your root layout:
// app/layout.tsx
import type { ReactNode } from 'react';
import '@kingtech/react-pdf-viewer/styles.css';
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
2. Configure the worker in a client module. The bundler emits the worker as an asset from new URL(…, import.meta.url):
// app/viewer.tsx
'use client';
import { configurePdfJs, PdfViewer } from '@kingtech/react-pdf-viewer';
configurePdfJs({
workerSrc: new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString(),
});
export function Viewer({ source }: { source: string }) {
return <PdfViewer source={source} />;
}
3. Render it from a page. Pages can stay Server Components:
// app/page.tsx
import { Viewer } from './viewer';
export default function Page() {
return <Viewer source="/report.pdf" />;
}
The package entry starts with 'use client', so a Server Component can also render PdfViewer directly with serializable props, such as a URL string. Callbacks, ref and configurePdfJs need a client module.
4. Optional: self-host the other assets. Copy the asset folders into public/pdfjs/, for example in a postinstall script:
{
"scripts": {
"postinstall": "node -e \"for (const d of ['cmaps','standard_fonts','wasm','iccs']) require('fs').cpSync('node_modules/pdfjs-dist/'+d, 'public/pdfjs/'+d, {recursive: true})\""
}
}
Then pass cMapUrl: '/pdfjs/cmaps/', standardFontDataUrl: '/pdfjs/standard_fonts/', wasmUrl: '/pdfjs/wasm/' and iccUrl: '/pdfjs/iccs/' to configurePdfJs, and add public/pdfjs/ to .gitignore.
Verify
- Run
next build. It must finish without “window is not defined” or similar server errors. - Open
.next/server/app/index.html: it containsclass="rpv-root"and the loading state, showing the viewer rendered on the server. - Run
next start, open the page and confirm the document renders and the worker loads from your origin.
Known issues
- Callbacks from a Server Component. Passing
onErroror other functions from a Server Component fails, because functions are not serializable. Render the viewer from a client module, as in step 2. - Document requests during prerendering. Nothing is fetched on the server; documents load in the browser after hydration, so the prerendered HTML shows the loading state.