How to add a PDF file preview to your (React) website and not lose your mind
It should be easy, right?
tl;dr I wanted to add a PDF file preview inside my web page, and I regretted all my life choices
I’ve recently had this idea to add a .pdf
file preview to my personal portfolio. Not to open it in a new tab, not to have it only available for a download, but to have the preview right there, sitting among other content of my web page.
The file is my CV, and the idea came from the fact that I started messing around with my portfolio, to expand and improve it. One of the ideas was to add more pages, one of them to be a dedicated page “For Recruiters” with my all skills and experiences, and to include a CV as a .pdf
file as well. Google search returned some YouTube videos and some npm packages, I tried a few and decided to write about it.
The way I didn’t want - nice and easy, using an <a>
tag
Using an anchor tag allows you to either open the file in a new tab, or to easily download it.
To open the file in a new tab:
<a href="/assets/files/chicken.pdf" target="_blank">Open in a new tab</a>
or to download it:
<a href="/assets/files/chicken.pdf" download>Download this file</a>
Problem is, this is not the behavior that I wanted.
The other easy way - use an <iframe>
- universal and simple solution
This brief and to the point video tutorial from dcode shows the simplest way to do this: add an <iframe>
to your page and display the .pdf
there. It’s easy enough and framework-agnostic, but it has a few drawbacks:
some browsers have built-in
.pdf
previewers, others do not and force a file downloadif the file is hosted externally, it may cause some CORS issues
<iframe>
has limited mobile support and may not work on some older mobile browsers (see: caniuse)
If you decide to use this method, simply add an <iframe>
tag to your HTML markup:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<iframe src="path/to/pdfFile.pdf" style={{ height: "100vh", width: "100%" }}></iframe>
</body>
</html>
This method also allows for searching the content of the file, which is neat. Here’s a Chrome preview (on the example of a timeless classic, the “chicken chicken” article):
The main problem with this approach is that I have no control whatsover of how the file preview is gonna look like on different browsers. It works and is simple, but there are other ways to achieve this.
The advanced way - using a library
There are several npm packages that handle .pdf
files. I tried pdf.js from Mozilla, but it felt a bit too complex in use and documentation isn’t that great. I tried react-pdf and it was a better, smoother experience. But still things didn’t work out of the box, there was an overflow on the page and the width of the file wasn’t responsive. So overall, I didn’t like it much. If you want to try it, here is some setup:
react-pdf
npm install react-pdf
in your
App.jsx
file, add those two lines:
import { pdfjs } from "react-pdf";
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`;
- Then create your component, for example:
// PDFViewer.jsx
import React, { useState } from "react";
import { Document, Page } from "react-pdf";
import 'react-pdf/dist/Page/AnnotationLayer.css';
import 'react-pdf/dist/Page/TextLayer.css';
export const ReactPDFViewer = ({ src }) => {
const [numPages, setNumPages] = useState<number>();
const [pageNumber, setPageNumber] = useState<number>(1);
function onDocumentLoadSuccess({ numPages }: { numPages: number }): void {
setNumPages(numPages);
}
function nextPage() {
setPageNumber((v) => ++v);
}
function prevPage() {
setPageNumber((v) => --v);
}
return (
<div style={{ width: "100vw", height: "100%" }}>
<button onClick={prevPage} disabled={pageNumber <= 1}>
Previous
</button>
<button onClick={nextPage} disabled={pageNumber >= (numPages ?? -1)}>
Next
</button>
<Document
file={src}
onLoadSuccess={onDocumentLoadSuccess}
className="my-react-pdf"
>
<Page pageNumber={pageNumber} />
</Document>
<p>
Page {pageNumber} of {numPages}
</p>
</div>
);
};
And here is the result:
Good things: it allows for viewing the pages with the buttons, which is nice, and the content of the file is searchable. But I failed to figure out where that overflow was coming from, and I don’t want any overflow on my portfolio. To fix the overflow, I run npm uninstall react-pdf
and just like that, the overflow was gone. Nice. (well, not really, because this package has MIT license).
Another npm package with MIT license is reactjs-pdf-reader. It looks easy enough to use from the docs, but…
Docs:
My project:
Oh well. At least this package doesn’t cause a mysterious overflow, so there is that.
react-pdf-viewer
That’s actually one of the first search results in google, but it’s paid, that’s why I wanted to be sure that I’ve exhausted all the other options before trying it. Its documentation is very clear and extensive, it has a ton of customization options, and costs only 50 bucks (in my home currency that’s a lot… so I can’t afford it). Too bad, because it’s great, works out of the box and is open source. But weirdly enough - open source, but you need a licence to use it? no comprende. Anyway, here is how to not set it up in your react project.
Go to https://react-pdf-viewer.dev/docs/getting-started/ and follow the steps.
npm install pdfjs-dist@3.4.120
npm install @react-pdf-viewer/core@3.12.0
Set up the Worker:
import { Worker } from '@react-pdf-viewer/core';
<Worker workerUrl="https://unpkg.com/pdfjs-dist@3.4.120/build/pdf.worker.min.js">
<!-- The viewer component will be put here -->
...
</Worker>
This can be done either at the root of your app, or in the component that will be rendering previews of pdfs.
- Import the Viewer and styles and use it!
import React from "react";
import { Worker, Viewer } from "@react-pdf-viewer/core";
import "@react-pdf-viewer/core/lib/styles/index.css";
export const PDFViewer = () => {
return (
<Worker workerUrl="https://unpkg.com/pdfjs-dist@3.4.120/build/pdf.worker.min.js">
<Viewer fileUrl="path/to/pdfFile.pdf" />
</Worker>
);
};
This can be extended by a plethora of options, like loader, theme, default scale and many more. There’s also plenty of plugins. What’s really nice about this package is that it automatically resizes the .pdf
viewbox to the size of the screen for better reading experience and that you can directly interact with the file content, as if you were using a standalone .pdf
reader. Here is a preview:
And it also looks great in dark mode (Mozilla):
Verdict
To sum up, the best FREE solution for my problem is to use an iframe. No hassle and it just works. If you have money, though, the best solution is the react-pdf-viewer package.
That’s it, happy coding!
Bonus
There is also a nice little library for creating pdfs online. https://react-pdf.org/
There is some library that is under development, but for now it only has a shiny landing page and an article on dev.to, but - useless - I cannot use it yet. https://www.react-pdf.dev/