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 download

  • if 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

  1. npm install react-pdf

  2. 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`;
  1. 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.

  1. Go to https://react-pdf-viewer.dev/docs/getting-started/ and follow the steps.

  2. npm install pdfjs-dist@3.4.120

  3. npm install @react-pdf-viewer/core@3.12.0

  4. 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.

  1. 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/