To render a PDF page in a browser with PDF.js, use its display API: load the library, configure a matching worker, load the PDF, get a page, size a canvas from the page viewport, and await the render task. The worker and display package must use the same version, and a web server—not a file:// URL—is needed for normal worker operation.
Choose the right PDF.js layer
PDF.js has a core layer, a display layer, and a viewer. For a custom browser integration, the display layer is the practical starting point: the PDF.js Getting Started documentation says it “takes the core layer and exposes an easier to use API to render PDFs and get other information out of a document.” The core layer handles parsing and interpretation, but the project describes direct use of it as advanced and says its API may change. The viewer is a complete user interface built on the display layer; use it as a starting point if you need an existing viewer rather than building your own controls.
The examples below use the display API. They show a custom canvas renderer, not a complete accessible document viewer with page navigation, text selection, search, annotations, and download controls.
Install PDF.js and make its worker available
PDF.js is distributed as prebuilt releases and through the pdfjs-dist npm package. The project’s setup guide recommends the latest official release for production; the correct import and worker paths can vary with the package version and bundler. The project’s Getting Started page listed stable v6.3.289 when accessed on September 29, 2026. Check the project’s releases when choosing a version, pin it in your application, and use a worker from that exact version.
#1 Best Overall
For a modern JavaScript application, install the distribution package:
npm install pdfjs-dist
Configure the worker URL in the same module where you import the display library. This Vite-style example uses the package’s worker entry as a URL; if your bundler handles worker assets differently, follow its asset-import convention and the PDF.js setup guide.
import * as pdfjsLib from 'pdfjs-dist';
import pdfWorkerUrl from 'pdfjs-dist/build/pdf.worker.mjs?url';
pdfjsLib.GlobalWorkerOptions.workerSrc = pdfWorkerUrl;
The ?url import syntax is bundler-specific, not a universal JavaScript or PDF.js requirement. Webpack and other build systems may need a different worker import or a separately copied worker asset. The invariant is that the browser can fetch the worker and that its version matches the display package. Use the project’s setup documentation for your bundler and release rather than mixing paths from unrelated versions.
Render one page to a canvas
The following example assumes the PDF is served from your application’s origin, and that the HTML contains a canvas with id="pdf-canvas". It renders page one at a scale of 1.5, the demonstration scale used by the official browser Hello World example—not a universal quality or performance setting.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallimport * as pdfjsLib from 'pdfjs-dist';
import pdfWorkerUrl from 'pdfjs-dist/build/pdf.worker.mjs?url';
pdfjsLib.GlobalWorkerOptions.workerSrc = pdfWorkerUrl;
const url = '/documents/sample.pdf';
const canvas = document.getElementById('pdf-canvas');
const context = canvas.getContext('2d');
async function renderFirstPage() {
const loadingTask = pdfjsLib.getDocument({ url });
const pdf = await loadingTask.promise;
const page = await pdf.getPage(1);
const scale = 1.5;
const viewport = page.getViewport({ scale });
const outputScale = window.devicePixelRatio || 1;
canvas.width = Math.floor(viewport.width * outputScale);
canvas.height = Math.floor(viewport.height * outputScale);
canvas.style.width = `${Math.floor(viewport.width)}px`;
canvas.style.height = `${Math.floor(viewport.height)}px`;
const transform = outputScale === 1
? null
: [outputScale, 0, 0, outputScale, 0, 0];
await page.render({
canvasContext: context,
transform,
viewport,
}).promise;
return { pdf, page, viewport };
}
renderFirstPage().catch((error) => {
console.error('Could not render the PDF page:', error);
});
Set workerSrc before loading the document. getDocument returns a loading task; its promise resolves to the PDF document. getPage(1) resolves to the first page, and getViewport calculates the page geometry at the chosen scale. The viewport’s width and height determine the canvas layout. Finally, page.render returns a render task; awaiting its promise means drawing has completed.
Rank #2
Page numbers start at 1. To render a different page, call pdf.getPage(pageNumber) after the document is loaded. Check the requested number against pdf.numPages if it comes from user input.
Choose canvas dimensions and scale deliberately
CSS size versus backing-store size
A canvas has both a CSS display size and a pixel backing store. For sharper output on a HiDPI display, the example multiplies the canvas backing dimensions by window.devicePixelRatio and passes a matching transform to the renderer. Its CSS dimensions remain at the viewport size. This preserves the intended layout while giving the browser more pixels to display. Omitting the transform when increasing the backing dimensions can leave the rendered content at the wrong scale.
Scale and memory
A larger viewport scale produces a larger page image and can improve detail, but also increases the canvas’s pixel dimensions and memory use. A smaller scale uses fewer pixels and may look less sharp when enlarged. Choose a scale based on the displayed size and device; the official example’s 1.5 is illustrative, not a recommended setting for every page or screen.
Free tools Windows power users keep installed
One-click scans. No signup required.
There are no controlled performance benchmarks established here for a particular scale, browser, or device. Measure your own application with representative documents and target devices if rendering speed or memory use is critical.
Load PDFs from a URL or from application data
The example passes a URL to getDocument. This is convenient when the PDF is hosted by your application, but the browser’s same-origin policy applies. If the PDF is on another origin, that server must allow the request through CORS, or your application can fetch it through a server-side proxy. A proxy also lets your backend enforce its own access controls; it should not be used to expose documents a visitor is not authorized to retrieve.
PDF.js can also receive document data rather than a URL. This can suit an upload flow where the application already has the file’s bytes. In that design, your application is responsible for obtaining and handling the data; it does not remove browser security requirements from any separate network request used to fetch the file.
Do not assume that every PDF is downloaded as one complete response before rendering starts. Depending on browser support and the server’s response headers, PDF.js may use HTTP range requests to retrieve the portions it needs. Server configuration and the document’s delivery path therefore affect loading behavior.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Render multiple pages without reusing a busy canvas
For a next/previous-page control, request the selected page and render it into the canvas. Do not start another render into that same canvas while an earlier render task is still in progress. The PDF.js walkthrough’s navigation example waits for one render to finish before drawing another page on the canvas.
let currentRenderTask;
async function renderPage(pdf, pageNumber, canvas, scale = 1.5) {
if (pageNumber < 1 || pageNumber > pdf.numPages) {
throw new RangeError(`Page must be between 1 and ${pdf.numPages}`);
}
if (currentRenderTask) {
await currentRenderTask.promise;
}
const page = await pdf.getPage(pageNumber);
const viewport = page.getViewport({ scale });
const context = canvas.getContext('2d');
canvas.width = Math.floor(viewport.width);
canvas.height = Math.floor(viewport.height);
canvas.style.width = `${Math.floor(viewport.width)}px`;
canvas.style.height = `${Math.floor(viewport.height)}px`;
currentRenderTask = page.render({ canvasContext: context, viewport });
await currentRenderTask.promise;
currentRenderTask = null;
}
This simple sequential pattern prevents overlapping work on one canvas. A production navigation control may also need to handle rapid user changes: keep track of the requested page, cancel obsolete rendering where appropriate, and make sure errors from cancelled tasks do not appear as unexpected failures. The key constraint is not to draw two pages concurrently into the same canvas.
Decide how much of a document to render
Rendering every page at full resolution immediately can consume substantial memory, especially for long documents. The PDF.js FAQ says: “The demo viewer creates, renders, and holds canvases only for visible pages to reduce the amount of used memory.” For a custom viewer, use that as a useful design principle: render the visible page or pages, then render additional pages as the reader navigates or scrolls. Reuse or release canvas resources for pages that are no longer needed if your UI does not require them to remain available.
Rank #4
- Render on demand: limits work and retained canvases, at the cost of rendering when a reader moves to a page.
- Pre-render nearby pages: can prepare likely next pages, but uses additional work and memory. Choose a small window based on your interface rather than rendering the entire document.
- Use the complete viewer: provides an existing interface to build on, while a custom display-API integration gives you more control and requires you to implement the interface yourself.
These are design trade-offs, not quantified performance claims; the project documentation does not establish a universal best number of pages to pre-render.
Run the application through a web server
Serve the application over HTTP during development. The PDF.js Getting Started guide says its worker is not enabled for file:// URLs and instructs users to use a server. Opening an HTML file directly can therefore fail even when the PDF and script appear to be present. Use your framework’s development server or another local HTTP server, then test the same deployment shape you intend to use in production.
Troubleshoot common rendering failures
API and worker version mismatch
Symptom: the console reports that the API version does not match the worker version, or worker initialization fails. Cause: the worker was cached from an older deployment, came from a different release, or its URL points to a separately versioned asset. Fix: pin the library and worker to the same PDF.js release, verify the URL served by the browser’s network panel, and clear stale cached worker assets when deploying an upgrade.
Worker does not start from a local file
Symptom: the page works inconsistently or reports worker-related errors when opened directly from disk. Cause: the app uses the file:// scheme. Fix: run it on a local web server and load it over HTTP.
Remote PDF request is blocked
Symptom: a same-origin PDF works, but a PDF hosted elsewhere fails to load. Cause: the remote server does not permit the browser’s cross-origin request. Fix: configure CORS on the PDF server or fetch the document through an application server proxy. The generic/demo viewer also blocks this functionality when deployed outside the PDF.js project’s own domain; that restriction is specific to the generic/demo viewer and is distinct from configuring your own display-API integration.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Canvas looks blurry or occupies too much space
Symptom: the page appears soft on a high-density display, or its layout is larger than expected. Cause: the backing-store dimensions, CSS dimensions, viewport scale, and render transform are being treated as interchangeable. Fix: keep CSS dimensions at the viewport size; when using a larger backing store for device-pixel ratio, scale both backing dimensions and the render transform by the same output scale.
Rendering fails when changing pages
Symptom: a render is cancelled or errors when the user navigates quickly. Cause: a new page render starts on a canvas that still has an active render task. Fix: serialize renders on that canvas by awaiting completion, or deliberately cancel the previous task before starting a replacement and handle the cancellation outcome.
Large documents consume too much memory
Symptom: memory grows as the viewer opens pages. Cause: too many large canvases are being created and retained. Fix: render visible pages as needed rather than rendering all pages up front, and limit how many off-screen canvases the application keeps.
Or skip the browser setup
If your goal is to capture a web page as an image or PDF rather than render an existing PDF inside your own interface, ScreenshotNeo is a separate option: it is a website screenshot API and MCP server, not a replacement for PDF.js’s in-app PDF viewer. A single GET request can return a screenshot or PDF. For example, using the supplied cURL pattern:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo documentation for request details. Cookie banners, newsletter popups, and chat widgets are removed before capture; bot checks, blank pages, failed loads, timeouts, and cache hits cost nothing. Its MCP server lets AI agents use the take_screenshot, get_page_info, and capture_pdf tools. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, with no card required.
Frequently Asked Questions
Can PDF.js render a PDF without showing its built-in viewer?
Yes. The display API lets an application render pages to its own canvas and build its own controls. The built-in viewer is optional.
Does the worker have to be hosted at the same URL as the PDF.js library?
No. It must be available to the browser and match the display package’s version; the worker and library do not have to share a URL.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.
Recommended Free Tools

