Skip to main content

Embed pdf in docusaurus

· 5 min read

This post shows how to embed a PDF file in a Docusaurus site or React-based application.

Sometimes, you may want to display a PDF file in your Docusaurus site or React-based application. It requires a package to embed a PDF file in HTML. The react-pdf package is a popular choice for this purpose.

Now, I will give a brief introduction to the installation and usage of the react-pdf package.

warning

For now, this package can only work well in Node version 22.X

Installation

You can install the react-pdf package using npm or yarn.

npm install react-pdf

Configuration

Once you have installed the react-pdf package, you need to set up the configuration for the PDF viewer.

If you are using Docusaurus, you can create a custom component to embed the PDF file under the src/components directory with the following two files.

src/components/PdfEmbed
├── index.tsx
└── style.module.css
src/components/PdfEmbed/index.tsx
index.tsx
import type {JSX, SetStateAction} from "react";
import {useCallback, useState} from "react";
import {pdfjs, Document, Outline, Page} from "react-pdf";
import "react-pdf/dist/esm/Page/AnnotationLayer.css";
import "react-pdf/dist/esm/Page/TextLayer.css";

import styles from "./style.module.css";
import React from "react";

pdfjs.GlobalWorkerOptions.workerSrc = new URL(
// 'pdfjs-dist/legacy/build/pdf.worker.min.mjs',
"pdfjs-dist/build/pdf.worker.min.mjs",
import.meta.url
).toString();

const options = {
cMapUrl: `https://unpkg.com/pdfjs-dist@${pdfjs.version}/cmaps/`,
standardFontDataUrl: `https://unpkg.com/pdfjs-dist@${pdfjs.version}/standard_fonts`,
};

type PDFFile = string | File | null;

interface PdfEmbedProps {
src: PDFFile;
}

export default function PdfEmbed({src}: PdfEmbedProps): JSX.Element {
const [numPages, setNumPages] = useState<number>();
const [pageNumber, setPageNumber] = useState(1);
const [searchText, setSearchText] = useState("");

function onDocumentLoadSuccess({numPages}) {
setNumPages(numPages);
setPageNumber(1);
}

function highlightPattern(text: string, pattern: string) {
return text.replace(pattern, (value) => `<mark>${value}</mark>`);
}

function changePage(offset: number) {
setPageNumber((prevPageNumber) => prevPageNumber + offset);
}

function onItemClick({pageNumber: itemPageNumber}) {
setPageNumber(itemPageNumber);
}

function previousPage() {
changePage(-1);
}

function nextPage() {
changePage(1);
}

const textRenderer = useCallback(
(textItem: {str: string;}) => highlightPattern(textItem.str, searchText),
[searchText]
);

function onChange(event: {target: {value: SetStateAction<string>;};}): void {
setSearchText(event.target.value);
}

return (
<div className={styles.container}>
<Document
file={src}
onLoadSuccess={onDocumentLoadSuccess}
options={options}
>
<div className={styles.documentViewer}>
<Outline onItemClick={onItemClick}/>
<Page
pageNumber={pageNumber}
customTextRenderer={textRenderer}
/>
</div>
</Document>
<div className={styles.pageControls}>
<button
disabled={pageNumber <= 1}
onClick={previousPage}
type="button"
>

</button>
<span>
{pageNumber} / {numPages}
</span>
<button
disabled={pageNumber >= numPages}
onClick={nextPage}
type="button"
>

</button>
</div>
<div className={styles.searchControls}>
<label htmlFor="search">Search:</label>
<input
type="search"
id="search"
value={searchText}
onChange={onChange}
/>
</div>
</div>
);
}

Also, the css file:

src/components/PdfEmbed/style.module.css
style.module.css
/* Base styles */
:root {
--shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
--rounded-corners: 6px;
--button-hover-bg: #f2f2f2;
--button-active-bg: #e2e2e2;
--control-bg-color: #ffffff;
--text-color: #333333;
}

/* Container needing to be set to a flex container to center the child */
.container {
display: flex;
justify-content: center;
align-items: center;
height: 100%; /* or a fixed height as per your design */
position: relative;
}

/* Page controls specific styles */
.pageControls {
position: absolute;
bottom: 0;
transform: translateX(-50%);
left: 50%;
background: white;
opacity: 0;
visibility: hidden;
transition: opacity 0.2s ease-in-out, visibility 0.2s ease-in-out;
box-shadow: var(--shadow);
border-radius: var(--rounded-corners);
z-index: 2;
display: flex;
align-items: center;
}

/* Parent hover effects */
.container:hover .pageControls {
opacity: 1;
visibility: visible;
pointer-events: auto;
}

.pageControls span {
font - size: 0.9em;
color: var(--text-color);
background: var(--control-bg-color);
padding: 0 0.5em;
}

.pageControls button {
width: 36px;
height: 36px;
background: var(--control-bg-color);
border: 0;
font-size: 0.9em;
color: var(--text-color);
border-radius: var(--rounded-corners);
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.3s, transform 0.2s;
}

.pageControls button:enabled:hover {
cursor: pointer;
background-color: var(--button-hover-bg);
}

.pageControls button:enabled:active {
background - color: var(--button-active-bg);
transform: scale(0.9);
}

.pageControls button:first-child {
border - top - right - radius: 0;
border-bottom-right-radius: 0;
}

.pageControls button:last-child {
border - top - left - radius: 0;
border-bottom-left-radius: 0;
}

.searchControls {
position: absolute;
bottom: 100%;
transform: translateX(-50%);
left: 100%;
background: white;
opacity: 0;
visibility: hidden;
transition: opacity 0.2s ease-in-out, visibility 0.2s ease-in-out;
box-shadow: var(--shadow);
border-radius: var(--rounded-corners);
z-index: 2;
display: flex;
align-items: center;
}

/* Parent hover effects */
.container:hover .searchControls {
opacity: 1;
visibility: visible;
}

.documentViewer {
display: flex; /* Establishes a flex container */
align-items: stretch; /* Align children full height of the container */
justify-content: start; /* Align children to the start of the flex container */
width: 100%; /* Takes full width of its parent container */
height: 100%; /* Optional: Adjust based on your layout needs */
}

.documentViewer > .react-pdf__Outline {
flex: 1; /* Allows the outline to grow and take up space */
border-right: 1px solid #ccc; /* Adds a separator between the outline and the page */
padding-right: 20px; /* Adds some padding for visual spacing */
overflow-y: auto; /* Adds scroll to the outline if content overflows */
}

.documentViewer > .react-pdf__Page {
flex: 3; /* Allows the page to take more space than the outline */
padding-left: 20px; /* Adds some padding for visual spacing */
}


/* Page controls specific styles */
.searchControls {
position: absolute;
bottom: 100%;
transform: translateX(-50%);
left: 100%;
background: white;
opacity: 0;
visibility: hidden;
transition: opacity 0.2s ease-in-out, visibility 0.2s ease-in-out;
box-shadow: var(--shadow);
border-radius: var(--rounded-corners);
z-index: 2;
display: flex;
align-items: center;
padding: 0.5em;
}

/* Parent hover effects */
.container:hover .searchControls {
opacity: 1;
visibility: visible;
pointer-events: auto;
}

Usage

After creating the custom component, you can use it to display a PDF file in your Docusaurus site or React - based application.

example.mdx
import PdfEmbed from '@site/src/components/PdfEmbed';
import pdf from './example.pdf';

# Displaying a PDF File

This MDX document uses the `Sample` component to display a PDF.

<PdfEmbed src={pdf} />

Example

Displaying a PDF File

This MDX document uses theSample component to display a PDF.

Loading PDF…
1 /