🔥 Understanding PDF.js Layers and How to Use them in React.js

Table of contents
- React PDF: Flexible and Powerful React PDF Viewer
- Overview of PDF.js Layers
- Step 1: Set up a React.js Project
- Step 2: Integrate PDF.js into a React project
- 1. Canvas Layer
- 2. Text Layer
- 3. Annotation Layer
- 4. Structural Layer
- How Layers Work Together
- Beyond the Fundamentals
- Conclusion
- React PDF: The PDF Viewer Built for React.js Developers 🚀

PDF files are everywhere but working with PDFs in a React.js project can be frustrating. You might run into clunky plugins, awkward iframes, or glitches that break the user experience. PDF.js gives you a powerful, open-source JavaScript library to display and interact with PDF files right in the browser.
Now, figuring out how to use PDF.js in your React project can be daunting at first. To leverage PDF.js's capabilities, it's important to understand how its layered structure works.
If you’ve ever wondered, “How do I highlight text selection?” or “How do I interact with links inside the PDF?” or even “How do I build advanced features like annotations?”, the answer usually lies in understanding these layers.
In this guide, you'll see how PDF.js splits content into layers, what those layers do, and how you can use them in your next React project. Let’s get going!
React PDF: Flexible and Powerful React PDF Viewer
Before diving into the technical side of things, let me introduce you to React PDF—a library I’ve been working on: React PDF is a handy PDF Viewer that renders PDFs right within your React.js or Next.js app. It packs over 20 features, including out-of-the-box default toolbar, customization and responsive design, so your users never have to leave your site to interact with your documents.
React PDF can integrated into various applications whether it's for a Document Management System, Workflow Management System, AI tool or simply as a PDF reader.
If that sounds interesting, I’d love for you to give React PDF a try. Your support helps me keep creating awesome tools and tutorials like this one. ❤️
Overview of PDF.js Layers
Before we dive in, Here’s a quick overview of the four main layers in PDF.js. Each layer handles a specific aspect of rendering or user interaction:
Canvas Layer: This layer draws the core visuals of your PDF—shapes, images, and text as graphics. It forms the base of your PDF Viewer.
Text Layer: Placed above the canvas, this layer lets users select and search text.
Annotation Layer: This layer manages interactive items like links, form fields, and highlights, so users can click, enter information, or move around the document.
Structural Layer: This layer keeps everything in order, handling layout, alignment, and scaling for all other layers.
By separating these roles into different layers, PDF.js stays organized and easy to update when you need to adjust or add features.
Let’s start by creating a fresh React project to integrate PDF.js.
Step 1: Set up a React.js Project
In this article, I use Codepen as a code editor. If you want to follow along, feel free to follow along the steps below.
Create a new Pen on CodePen.
Click the Settings button (top-right corner of the editor).
In the Pen Settings dialog:
Go to the JavaScript tab.
Set the JavaScript Preprocessor to 'Babel'.
Under 'Add External Scripts / Pens', search for and add:
react
&react-dom
Click Save & Close.
Now you can write React code using JSX in the JavaScript editor like below.
Step 2: Integrate PDF.js into a React project
First, let’s configure your React project to use the pdfjs-dist
library.
Open Pen Settings: Click on the Settings button at the top of your Pen.
Go to JavaScript Tab: In the popup, switch to the JS tab.
Add Packages In the Add Packages box, search for and add
pdfjs-dist
.Save and Close: Click the button to apply changes
Update Import Statement CodePen might auto-generate an import that doesn't work correctly. Replace it like so
// ❌ Wrong (auto-generated)
// import pdfjsDist from "https://esm.sh/pdfjs-dist";
// âś… Correct
import * as PDFJS from "https://esm.sh/pdfjs-dist";
- Add PDF.js Worker and Configure It You'll need to explicitly import and configure the worker. Here's the complete setup
import React from "https://esm.sh/react";
import ReactDOM from "https://esm.sh/react-dom/client";
import * as PDFJS from "https://esm.sh/pdfjs-dist";
import * as PDFWorker from "https://esm.sh/pdfjs-dist/build/pdf.worker.min";
try {
PDFJS.GlobalWorkerOptions.workerSrc = PDFWorker;
} catch (e) {
window.pdfjsWorker = PDFWorker;
}
Explanation Before loading the PDF, we need to configure the PDF worker. The worker is responsible for processing the PDF in a separate thread, improving performance.
That’s it! You’re now set up to start experimenting with PDF.js layers within a React environment. So let's get to the layers.
1. Canvas Layer
Purpose
The Canvas Layer is the foundation—it’s where PDF.js draws the visual elements such as images, shapes, and text (as graphics). Essentially, what you see on-screen is coming from this layer.
How It Works
PDF.js uses the HTML
<canvas>
element to display the PDF's visual contentIt utilizes the browser's 2D canvas API for high-performance rendering.
This means your PDF content will look consistent and accurate across various screen sizes and devices.
Use Cases
Ideal for non-interactive or static PDF content.
It renders the document as an image, ensuring PDF’s fonts, colors, and layouts are consistent across all devices.
Example of Canvas Layer Code with React
Here’s a quick CodePen demo showcasing a minimal Canvas Layer setup in React.js.
{% codepen https://codepen.io/9haroon/pen/jEOppVz %}
📜 How the Code Works
Below, I’ll walk through the major steps for rendering a PDF page onto the canvas.
(1) Fetching and Loading the PDF
const PDF_SRC = "https://pdfobject.com/pdf/pdf_open_parameters_acro8.pdf"
...
React.useEffect(() => {
processLoadingTask(PDF_SRC)
}, [])
function processLoadingTask(source) {
const loadingTask = PDFJS.getDocument(source);
loadingTask.promise
.then((docProxy) => { ... })
.then((page) => {
...
canvasLayer.current.width = width;
canvasLayer.current.height = height;
const context = canvasLayer.current.getContext("2d");
const renderContext = {
canvasContext: context,
viewport: viewport
};
return page.render(renderContext);
})
}
Explanation:
The
processLoadingTask
method starts the PDF loading process usingPDFJS.getDocument(source)
frompdfjs-dist
.Once the PDF is loaded from
PDF_SRC
, we obtain adocProxy
—a PDF document instance that contains all pages of the PDF.In this example, only the first page is used for rendering.
The total number of pages is retrieved from
docProxy.numPages
and stored in thetotalPages
state.
(2) Rendering the Canvas Layer
...
const canvasLayer = React.useRef()
const [pdfDimension, setPdfDimension] = React.useState({
width: 0,
height: 0
});
...
return (
...
<div
class="pdf__canvas-layer"
style={{
height: `${pdfDimension.height}px`,
width: `${pdfDimension.width}px`,
border: '1px solid #dfdfdf',
margin: '0 auto'
}}
>
<canvas ref={canvasLayer} />
</div>
...
)
Explanation
After the PDF is loaded and we have the first page object then:
Scales the canvas to match the PDF page dimensions by updating
pdfDimension
.A
<canvas>
element (canvasLayer) is used to render the PDF page as an image.The rendering process is handled by
page.render(renderContext)
, wherecanvasContext
paints the page content onto the canvas.
This ensures the final output accurately matches the original PDF’s appearance.
2. Text Layer
Purpose
The Text Layer ensures that the text in your PDF is selectable, searchable, and accessible. While the Canvas Layer does the heavy lifting of rendering visuals, the Text Layer is what lets you highlight and copy actual text.
How It Works
PDF.js extracts the text content separately from the PDF.
It positions each piece of text exactly over the underlying canvas, using absolutely positioned
<div>
elements.Even though you might not see these text
<div>
visually (they’re often transparent), they’re there to support selection and searching.
Use Cases
Enable text selection and copying from the PDF.
Text layer is crucial for accessibility, as it enables screen readers to interpret the content.
Provide text searching and highlighting within the PDF viewer.
Keep the text aligned with the rendered PDF page
Text layer Code Example
Here’s a React.js CodePen example that demonstrates a Text Layer in action:
{% codepen https://codepen.io/9haroon/pen/NPPyMQm %}
With this text layer, you can use your mouse to select text directly on the PDF.
###📜 How the Code Works
(1) Fetching and Loading the PDF
Identical to the steps in the Canvas Layer—we load the PDF document first.
(2) Structuring the Layers
return (
...
<div class="pdf-layers" style={{ ... }} >
<div class="pdf-layer__canvas">
<canvas ref={canvasLayer} />
</div>
<div ref={textLayer} class="pdf-layer__text" />
</div>
)
Explanation:
The
pdf-layers
is a container that holds both the Canvas Layer and the Text Layer.The Canvas Layer (canvasLayer) displays the PDF page visually.
The Text Layer (textLayer) is styled to sit on top of the canvas, enabling text selection.
(3) Rendering the Text Layer
function renderTextLayer(page) {
const viewport = page.getViewport({ scale: 1 });
const { scale } = viewport;
textLayer.current.style.setProperty("--total-scale-factor", `${scale}`);
page.getTextContent().then((content) => {
const renderTask = new PDFJS.TextLayer({
container: textLayer.current,
textContentSource: content,
viewport: viewport.clone({ dontFlip: true })
});
return renderTask.render();
});
}
Explanation:
Render the text layer with
renderTextLayer(page)
function.page.getTextContent()
retrieves the text data from the PDF page.This data is passed into a
new PDFJS.TextLayer
, specifying:container
: The DOM element where the text will be rendered.textContentSource
: Extracted text data from the PDF page.viewport
: Define text positioning and scaling.
renderTask.render()
overlays invisible but selectable text<div>
over the canvas.Check the CSS panel in CodePen to understand how the canvas and text layers are stacked together.
3. Annotation Layer
Purpose
The Annotation Layer handles interactive elements like hyperlinks, highlights, form fields, and comments. If you need your PDF viewer to support internal linking (jumping to different pages), or if your PDFs have fillable forms, this layer is for you.
How It Works
PDF.js extracts annotation data (links, form fields, etc.) from the PDF.
It overlays these data as interactive HTML elements (e.g.
<a>
,<input>
,<textarea>
), which sit above the Canvas Layer and Text Layer positioned using CSS.This layer ensures that users can click, type, and interact without altering the PDF’s static visuals.
Use Cases
Clicking on links within PDF to navigate between pages.
Rendering highlights, underlines, and notes from PDF annotations.
Displaying interactive form fields within a PDF.
Annotation layer Code Example
Check out this CodePen example on the Annotation Layer for React.js:
You can click the links inside the Codepen to change the PDF's page.
{% codepen https://codepen.io/9haroon/pen/oggEyma %}
📜 How the Code Works
(1) Fetching and Loading the PDF
Again, the same initial PDF loading steps as before.
(2) Structuring the Layers
return (
...
<div class="pdf-layers" style={{ ... }} >
<div class="pdf-layer__canvas">
<canvas ref={canvasLayer} />
</div>
<div ref={textLayer} class="pdf-layer__text" />
<div ref={annotationLayer} class="pdf-layer__annotation" />
</div>
)
Explanation:
We add another
<div>
for the Annotation Layer withinpdf-layers
.The Annotation Layer is positioned on top of the Canvas Layer and Text Layer.
(3) Rendering the Annotation Layer
....
const annotationLayer = React.useRef();
...
function getAnnotationsAsync(page) {
return page.getAnnotations({ intent: "display" });
}
function renderAnnotationLayer(page) {
const viewport = page.getViewport({ scale: 1 });
annotationLayer.current.replaceChildren();
getAnnotationsAsync(page).then((annotations) => {
annotations.current = annotations;
const pdfAnnotationLayer = new PDFJS.AnnotationLayer({
div: annotationLayer.current,
...
});
return pdfAnnotationLayer.render({
...
annotations,
...
});
});
}
Explanation:
getAnnotationsAsync()
retrieves all annotation data from the PDF page—such as links, form fields, and highlights.The
viewport
is cloned usingviewport.clone({ dontFlip: true })
to ensure correct positioning and scaling for the annotation layer.renderAnnotationLayer(page)
handles rendering by:Clearing or replacing any existing annotation elements inside the
annotationLayer.current
.Creating a new
PDFJS.AnnotationLayer
instance to manage rendering.Calling
pdfAnnotationLayer.render({ ... })
with: The annotations retrieved from the page, the cloned viewport, the container where annotations should appear, and so on.
This ensures that annotations are accurately rendered on top of the PDF page.
(4) Handling Internal Link Clicks
...
const annotationEventAbortCtrl = React.useRef();
...
function bindAnnotationLayerEvent(page) {
if (annotationEventAbortCtrl.current) {
// Already binded
return;
}
annotationEventAbortCtrl.current = new AbortController();
const { signal } = annotationEventAbortCtrl.current;
annotationLayer.current.addEventListener(
"click",
async (event) => {
let annotationTarget = event.target.parentNode;
if (!annotationTarget) {
return;
}
const id = annotationTarget.dataset.annotationId;
if (!id) {
return;
}
const annotationLinkId = (await getAnnotationsAsync(page)).find(
(ele) => ele.id === id
);
if (!annotationLinkId) {
return;
}
const pageIndex = await pdfDocProxy.current.getPageIndex(
annotationLinkId.dest[0]
);
setCurrentPage(pageIndex + 1);
},
{ signal }
);
}
Explanation:
This function listens for clicks on internal links (annotations) in the PDF.
When a user clicks a link, it checks if the clicked target has an associated
annotationId
.If found, it looks up the annotation to get its destination using
getAnnotationsAsync(page)
.Then it uses
getPageIndex()
to find which page the link points to.Finally, it updates the current page with
setCurrentPage()
to navigate to the target page.
(5) Handling Page Navigation
A simple navigation bar lets users switch between pages.
...
const [currentPage, setCurrentPage] = React.useState(3);
const [totalPages, setTotalPages] = React.useState(0);
...
React.useEffect(() => {
processRenderPdfLayers(PDF_SRC);
}, [currentPage]);
...
return (
<div class="page-navigation">
<button
disabled={currentPage <= 1}
onClick={() => setCurrentPage(currentPage - 1)}
>
←
</button>
<span>
{currentPage} / {totalPages}
</span>
<button
disabled={currentPage >= totalPages}
onClick={() => setCurrentPage(currentPage + 1)}
>
→
</button>
</div>
)
Explanation:
The
currentPage
value updates when navigation buttons are clicked.The
useEffect
withcurrentPage
dependency ensures that all layers are re-rendered when the page changes.This ensures all layers—Canvas, Text, and Annotation—stay in sync.
4. Structural Layer
Purpose
Think of the Structural Layer as the master layout manager. It keeps the Canvas, Text, and Annotation layers correctly aligned, scaled, and positioned when users zoom in or out, or when the window is resized.
This layer is a must-have when creating a PDF viewer as it serves as the foundation that glues all other layers together.
How It Works
The Structural Layer is typically implemented by a container element (
<div>
) that wraps all other layers.Maintain consistent positioning among layers so they don’t drift apart during zooms or scrolls.
Act as the foundation that ties everything together, so your PDF viewer remains cohesive.
Use Cases
Ensure a consistent user experience across different screen sizes and resolutions.
Enable smooth zooming by scaling all layers proportionally.
Centralize navigation and layout logic (e.g. controlling how the viewer scrolls from one page to another.)
In our code examples, the <div ref="pdfLayersWrapper" class="pdf__layers">
element serves as the Structural Layer, wrapping for other layers:
<div ref="pdfLayersWrapper" class="pdf__layers">
<div class="pdf__canvas-layer">
<canvas ref="canvasLayer" />
</div>
<div ref="textLayer" class="pdf__text-layer"></div>
<div ref="annotationLayer" class="pdf__annotation-layer"></div>
</div>
Why is this important?
The Structural Layer wrapper ensures the Canvas, Text, and Annotation Layers all stay in sync, even when you zoom or flip through pages.
You can customize the wrapper to handle special behaviors like lazy-loading pages, scroll snapping and other performance optimizations.
How Layers Work Together
Here’s how all four layers in PDF.js work together in harmony:
Rendering Process: Each page is first drawn onto the Canvas Layer (visual content). Next, the Text Layer is layered on top to enable selection and searching. Finally, the Annotation Layer is placed at the very top, providing interactive elements like links and form fields.
Interactivity: The Annotation Layer handles user events (like clicking on links or typing into form fields), while the Text Layer ensures text can be highlighted, copied, or searched.
PDF Viewer: The Structural Layer wraps everything up, ensuring that zooming, resizing, and navigation across pages remain consistent. It keeps all layers aligned and responsive, so users get a polished viewing experience.
Beyond the Fundamentals
PDF.js gives you the basics for working with PDFs in JavaScript, but plugging it into a React project means you'll want more control.
Customize UI: Use React hooks and state to create a smooth, custom viewer that fits right into your app.
Handling Large PDFs: For large PDFs, skip loading the whole file up front. Choose lazy loading or virtualization to keep your app feeling quick, even with hundreds of pages.
Adding Toolbar Control: Build your own toolbar with features like zoom, page jumps, and download options, so users get a great experience.
With the right setup, PDF.js and React work together to handle big files and let you shape the viewer to fit your project.
Conclusion
I’ve worked with PDF.js across many React.js projects, from straightforward file previews to detailed, interactive document tools. Its layered structure is always the foundation.
This setup matters because you get full control over each part of the PDF viewer. You can tune specific layers to boost performance or improve the user interface. It’s easy to refine how your viewer responds to things like search, form input, or user actions.
By understanding these layers, you will be able to optimize PDF.js implementations, customize features, and deliver a better user experience. Whether you’re building a simple React PDF Viewer or a complex Document Processing System, leveraging these layers effectively will set your project up for success.
React PDF: The PDF Viewer Built for React.js Developers 🚀
If you enjoyed this article, I’d love for you to check out React PDF. It’s a PDF Viewer built from the ground up for React and Next.js applications. Whether you’re just starting out or already running a large-scale app, React PDF got you covered.
Designed with developers in mind, React PDF offers:
Out-of-the-box features to get you up and running fast.
Advanced customization so you can nail that perfect theme and feel.
Responsive layouts that adapt to any device.
Developer-friendly APIs for more flexibility.
Your support encourages me to keep building tools and publish content for the React community. Thank you for checking out React PDF in advance! 🙏
Subscribe to my newsletter
Read articles from Kittisak Ma directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
