Blog Post

How to build a TypeScript PDF viewer with PDF.js

Illustration: How to build a TypeScript PDF viewer with PDF.js
Information

This article was first published in September 2021 and was updated in October 2024.

In this tutorial, we’ll show how to build a TypeScript PDF viewer with PDF.js, one of the most popular open source libraries for rendering PDF files in the browser.

Developers like TypeScript due to its type safety and the fact that it’s a superset of JavaScript. TypeScript adds static typing to JavaScript, which allows developers to catch errors at compile time instead of run time.

In the first part of this tutorial, we’ll walk through how to render a PDF in the browser with PDF.js and TypeScript. In the second part, we’ll look at how to build a fully featured PDF viewer with the Nutrient TypeScript PDF library. Our PDF viewer library provides some additional benefits beyond what PDF.js provides, including:

  • A prebuilt and polished UI for an improved user experience
  • 15+ prebuilt annotation tools to enable document collaboration
  • Browser-based text editing, page cropping, merging, rotating, and more
  • Support for more file types with client-side PDF, MS Office, and image viewing
  • Dedicated support from engineers to speed up integration

Requirements

To get started, you’ll need:

  • Node.js

  • A package manager for installing the Nutrient library. You can use npm or Yarn. When you install Node.js, npm is installed by default.

  • TypeScript

You can install TypeScript globally by running the following command:

npm install -g typescript

Building a TypeScript PDF viewer with PDF.js

PDF.js is a JavaScript library built by Mozilla, and it allows you to create a full-featured PDF viewer in the browser using JavaScript and the HTML5 <canvas> element. You can integrate PDF.js with different JavaScript frameworks and libraries like React, Angular, and Vue.js.

Getting started

  1. Create a new folder on your computer and change your directory to the project:

mkdir typescript-pdf-viewer
cd typescript-pdf-viewer
  1. Next, run npm init --yes to create a package.json file.

  2. Create a new tsconfig.json configuration file at the root of your project:

tsc --init

You can customize the rules you want the TypeScript compiler to follow. Here’s an example configuration:

{
	"compilerOptions": {
		"target": "esnext",
		"module": "es6",
		"esModuleInterop": true,
		"forceConsistentCasingInFileNames": true,
		"strict": true,
		"skipLibCheck": true,
		"removeComments": true,
		"preserveConstEnums": true,
		"sourceMap": true,
		"noImplicitAny": true,
		"strictNullChecks": true,
		"moduleResolution": "node"
	},
	"include": ["src/**/*"]
}

With this configuration, the compiled JavaScript code will target the latest version of ECMAScript standards for JavaScript, and it’ll use the ES6 import and export modules. The include array tells TypeScript to compile everything in the src folder.

Installing PDF.js and configuring webpack

  1. You’ll use webpack to bundle your project. Start by installing the necessary dev dependencies:

npm i -D webpack webpack-cli webpack-dev-server ts-loader typescript html-webpack-plugin cross-env copy-webpack-plugin clean-webpack-plugin

Here’s what’s installed:

  • webpack — The webpack bundler.

  • webpack-cli — Command-line interface for webpack.

  • webpack-dev-server — A local server to run webpack in the browser with live reloading.

  • ts-loader — A package that teaches webpack how to compile TypeScript.

  • typescript — The TypeScript compiler.

  • clean-webpack-plugin — A plugin that cleans the output directory before building.

  • copy-webpack-plugin — A plugin that copies files and directories to the output directory.

  • html-webpack-plugin — A plugin that generates an HTML file from a template.

  • cross-env — A package that allows you to set environment variables.

After the installation, your package.json file will look like this:

"devDependencies": {
    "clean-webpack-plugin": "^4.0.0",
    "copy-webpack-plugin": "^12.0.2",
    "cross-env": "^7.0.3",
    "html-webpack-plugin": "^5.6.0",
    "ts-loader": "^9.5.1",
    "typescript": "^5.6.3",
    "webpack": "^5.95.0",
    "webpack-cli": "^5.1.4",
    "webpack-dev-server": "^5.1.0"
  },
  1. Now, configure webpack by creating a webpack.config.js file at the root of your project. This will define how your project is built:

// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const CleanPlugin = require('clean-webpack-plugin');

module.exports = {
	entry: path.resolve(__dirname, './src/index.ts'),
	output: {
		path: path.resolve(__dirname, 'dist'),
		filename: '[name].js',
	},
	devtool: 'inline-source-map',
	mode: 'development',
	module: {
		rules: [
			// All files with a `.ts` or `.tsx` extension will be handled by `ts-loader`.
			{
				test: /\.tsx?$/,
				use: { loader: 'ts-loader' },
				exclude: /node_modules/,
			},
			{
				test: /\.mjs$/,
				include: /node_modules/,
				type: 'javascript/auto',
			},
		],
	},
	resolve: {
		extensions: ['.ts', '.tsx', '.js'],
	},
	plugins: [
		new CleanPlugin.CleanWebpackPlugin(),
		// Automatically insert <script src="[name].js"></script> into the page.
		new HtmlWebpackPlugin({
			template: './src/index.html',
		}),
		// Copy the PDF file and PDF.js worker to the output path.
		new CopyWebpackPlugin({
			patterns: [
				{
					from: './src/example.pdf',
					to: './example.pdf',
				},
				{
					from: './node_modules/pdfjs-dist/build/pdf.worker.mjs',
					to: './main.worker.js',
				},
			],
		}),
	],
};

This configuration specifies that your entry point is src/index.ts, and the output will be placed in the dist folder. The .mjs rule is important for handling the PDF.js worker, and you’re copying both the PDF file and the worker to the output directory.

  1. Install pdfjs-dist as a dependency:

npm install pdfjs-dist

Since pdfjs-dist provides its own type definitions, you don’t need to install extra TypeScript typings.

Rendering a PDF

After setting up the project with webpack and TypeScript, it’s time to start using PDF.js.

  1. Create the src directory, and inside it, create an index.html file:

mkdir src && touch src/index.html
  1. In the index.html file, add a <canvas> element where the PDF will be rendered:

<!DOCTYPE html>
<html lang="en">
	<head>
		<title>PDF.js Example</title>
	</head>
	<body>
		<canvas id="pdf"></canvas>
	</body>
</html>
  1. Now, create an index.ts file inside the src directory. This file will contain the logic for rendering the PDF:

import * as pdfjsLib from 'pdfjs-dist';

pdfjsLib.GlobalWorkerOptions.workerSrc = './main.worker.js';

(async () => {
	const loadingTask = pdfjsLib.getDocument('example.pdf');
	const pdf = await loadingTask.promise;

	// Load the first page.
	const page = await pdf.getPage(1);

	const scale = 1;
	const viewport = page.getViewport({ scale });

	// Set the canvas dimensions.
	const canvas = document.getElementById('pdf') as HTMLCanvasElement;
	const context = canvas.getContext('2d');
	canvas.height = viewport.height;
	canvas.width = viewport.width;

	// Render the page into the canvas.
	const renderContext = {
		canvasContext: context,
		viewport: viewport,
	};
	await page.render(renderContext);
	console.log('Page rendered!');
})();

Running the project

  1. Add some scripts to your package.json file to build and run the project:

"scripts": {
   "build": "cross-env NODE_ENV=production webpack --config webpack.config.js",
   "dev": "webpack serve --config webpack.config.js",
   "start": "serve -l 8080 ./dist"
}
  1. Place the PDF file (example.pdf) in the src directory before running the project.

  2. To run the project:

  • For development with live reloading, use:

npm run dev
  • To serve the built files, use:

npm start

Navigate to http://localhost:8080 to see the PDF rendered in your browser!

Resulting page

Information

You can access the full code on GitHub.

Building a TypeScript PDF viewer with Nutrient

We offer a commercial TypeScript PDF library that can easily be integrated into your web application. It comes with 30+ features that let you view, annotate, edit, and sign documents directly in your browser. Out of the box, it has a polished and flexible UI that you can extend or simplify based on your unique use case.

Getting started

  1. Create a new folder and change your directory to it:

mkdir typescript-pspdfkit-viewer

cd typescript-pspdfkit-viewer
  1. Similar to what you did above, create a package.json file by running npm init --yes.

  2. Create a tsconfig.json file and use the following configuration:

{
	"compilerOptions": {
		"removeComments": true,
		"preserveConstEnums": true,
		"module": "commonjs",
		"target": "es5",
		"sourceMap": true,
		"noImplicitAny": true,
		"esModuleInterop": true
	},
	"include": ["src/**/*"]
}

Installing Nutrient and configuring webpack

  1. Install the Nutrient Web SDK library as a dependency with npm or yarn:

npm install pspdfkit
  1. Install the necessary dependencies for webpack, create a config directory, and place your webpack configuration file inside it:

mkdir config && touch config/webpack.js
  1. If you’re using webpack 4, use the example file. If you’re using the latest version of webpack — currently ^5.72.0 — use the following configuration:

// webpack.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');

const filesToCopy = [
	// Nutrient files.
	{
		from: './node_modules/pspdfkit/dist/pspdfkit-lib',
		to: './pspdfkit-lib',
	},
	// Application CSS.
	{
		from: './src/index.css',
		to: './index.css',
	},
	// Example PDF.
	{
		from: './assets/example.pdf',
		to: './example.pdf',
	},
];

/**
 * webpack main configuration object.
 */
const config = {
	entry: path.resolve(__dirname, '../src/index.ts'),
	mode: 'development',
	devtool: 'inline-source-map',
	output: {
		path: path.resolve(__dirname, '../dist'),
		filename: '[name].js',
	},
	resolve: {
		extensions: ['.ts', '.tsx', '.js'],
	},
	module: {
		rules: [
			// All files with a `.ts` or `.tsx` extension will be handled by `ts-loader`.
			{
				test: /\.tsx?$/,
				loader: 'ts-loader',
				exclude: /node_modules/,
			},
		],
	},
	plugins: [
		// Automatically insert <script src="[name].js"></script> into the page.
		new HtmlWebpackPlugin({
			template: './src/index.html',
		}),

		// Copy the WASM/ASM and CSS files to the `output.path`.
		new CopyWebpackPlugin({ patterns: filesToCopy }),
	],

	optimization: {
		splitChunks: {
			cacheGroups: {
				// Creates a `vendor.js` bundle that contains external libraries (including `pspdfkit.js`).
				vendor: {
					test: /node_modules/,
					chunks: 'initial',
					name: 'vendor',
					priority: 10,
					enforce: true,
				},
			},
		},
	},
};

module.exports = config;

Displaying the PDF

  1. Add the PDF document you want to display to the assets directory. You can use our demo document as an example.

  2. Create an index.html file inside the src directory and add the following code:

<!DOCTYPE html>
<html>
	<head>
		<title>PSPDFKit for Web — TypeScript example</title>
		<link rel="stylesheet" href="index.css" />
	</head>
	<body>
		<div class="container"></div>
	</body>
</html>

This adds an empty <div> element to where Nutrient will be mounted.

  1. Declare the height of this element in your CSS file like this:

.container {
	height: 100vh;
}
  1. Now, create an index.ts file inside the src directory:

import PSPDFKit from 'pspdfkit';

function load(document: string) {
	console.log(`Loading ${document}...`);
	PSPDFKit.load({
		document,
		container: '.container',
	})
		.then((instance) => {
			console.log('PSPDFKit loaded', instance);
		})
		.catch(console.error);
}

load('example.pdf');

Here, you’ve imported the PSPDFKit library and created a function that loads the PDF document.

Running the project

  1. Now, write some scripts in the package.json file to start your server:

"scripts": {
    "build": "cross-env NODE_ENV=production webpack --config config/webpack.js",
    "prestart": "npm run build",
    "dev": "tsc",
    "start": "serve -l 8080 ./dist"
},
  1. Run npm start to start the server. Navigate to http://localhost:8080 to see the contents of the dist directory.

Resulting page

Opening different PDF files

The current viewer is loading the PDF file you have in the assets/example.pdf file. But you can also open different PDF files by adding an input field with the file type on the index.html file:

<div>
	<input type="file" class="chooseFile" accept="application/pdf" />
</div>
<br />
<div class="container"></div>

Now, go to the index.ts file and add the following code:

interface HTMLInputEvent extends Event {
	target: HTMLInputElement & EventTarget;
}

let objectUrl = '';

document.addEventListener('change', function (event: HTMLInputEvent) {
	if (
		event.target &&
		event.target.className === 'chooseFile' &&
		event.target.files instanceof FileList
	) {
		PSPDFKit.unload('.container');

		if (objectUrl) {
			URL.revokeObjectURL(objectUrl);
		}

		objectUrl = URL.createObjectURL(event.target.files[0]);
		load(objectUrl);
	}
});

Here, you’ve added a change event listener to the <input> element. When the user selects a file, you’ll unload the current PDF and load the new one.

Information

If you want to learn how to add annotations to your application, check out the Open and annotate PDFs in a TypeScript app blog post. You can see the example project with annotation support on GitHub.

Screenshot showing Choose File Example

Adding even more capabilities

Once you’ve deployed your viewer, you can start customizing it to meet your specific requirements or easily add more capabilities. To help you get started, here are some of our most popular TypeScript guides:

Conclusion

In this tutorial, you saw how to build a TypeScript PDF viewer — first with PDF.js, and then with the Nutrient TypeScript PDF viewer library that allows you to display and render PDF files in your TypeScript application.

We created similar how-to blog posts using different web frameworks and libraries:

To get started with our TypeScript PDF viewer, try it for free, or launch our web demo.

FAQ

Here are a few frequently asked questions about building a TypeScript PDF viewer.

What are the main steps to build a TypeScript PDF viewer with PDF.js?

  1. Set up a project with Node.js, TypeScript, and webpack.

  2. Install and configure PDF.js.

  3. Render a PDF file in a <canvas> element using TypeScript.

What additional features does Nutrient Web SDK offer compared to PDF.js?

Nutrient Web SDK offers a prebuilt UI, annotation tools, document editing capabilities, support for various file types, and dedicated engineering support.

How can I get started with Nutrient Web SDK?

  1. Create a new project folder and set up the project with Node.js and TypeScript.

  2. Install Nutrient and configure webpack.

  3. Integrate Nutrient into your application to display and interact with PDF documents.

Where can I find the full code for the examples in this tutorial?

You can access the full code for the PDF.js example on GitHub and the Nutrient example on GitHub.

Author
Hulya Masharipov Technical Writer

Hulya is a frontend web developer and technical writer at PSPDFKit who enjoys creating responsive, scalable, and maintainable web experiences. She’s passionate about open source, web accessibility, cybersecurity privacy, and blockchain.

Related products
Share post
Free trial Ready to get started?
Free trial