How to create PDFs from React components [client side solution]

If you have ever dealt with a B2B dashboard app, you know that businesses love reports in pdf format.

Base photo by Lauren Richmond on Unsplash

When I first faced this issue, we had an analytics page, which we wanted to convert into a pdf. My instinct was to add a print.css file and let the browser handle all the dirty work. Long story short, this doesn’t work.

With a print specific stylesheet, I found myself nullifying most of my web specific styles. The end result was not much appealing either. Also, it’s hard to style contents of a PDF as html. What renders fine in the browser looks terrible on a PDF.

We started generating PDFs on server side, using a PhantomJS instance. You can see this server side approach in action at PFRepo : https://pfrepo.me. It’s used to generate pdf version of web resumes)

A better way is to render pdf directly on the frontend. The client side approach is fast (nothing to transmit over network), has less moving parts (no headless browsers) and precise. It also lets you continue to design html instead of thinking in terms of PDF.

Generating a pdf on the client side is a 3 step process :

  • Convert the DOM into svg
  • Convert the svg into png
  • Convert the png into pdf

I’m using React in this example, but the same approach can work with Angular, Vue or any other frontend system. We just need vanilla js.

In the IDE below, you can see two rendered pages. One assuming that our content fits on a single page, and the other where the size of the content might be dynamic and need more that one pages.

Step 1: Convert the A4 DOM into svg

html2canvas is a good library to convert DOM to svg. The api is fairly simple.

Converting html to svg

Step 2: Convert svg to png

This can be done using vanilla js:

const input = document.getElementById('divIdToPrint');
html2canvas(input)
.then((canvas) => {
const imgData = canvas.toDataURL('image/png');
})
;

Step 3: Convert png to pdf

This is achieved by a library called jsPDF. The usage modifies step 2 code to the following:

html2canvas(input)
.then((canvas) => {
const imgData = canvas.toDataURL('image/png');
    const pdf = new jsPDF();
pdf.addImage(imgData, 'PNG', 0, 0);
pdf.save("download.pdf");

});
;

Handling multiple pages

Ok, so we have the ability to print DOM to pdf, but our current setup can only print single pages. What if we have multiple pages ?

There are two ways to go about this :

> The easy way

Print an elongated pdf and let the system handle page breaks.

> The right way

Do some quick math to figure out how many pages are needed. Calculate the offsets and heights of the pages and run the single page process over all the pages.

The result

The StackBlitz IDE below has the code and ui in action. I handled multiple pages the easy way after giving up on the right way (which is present in the code).

Stackblitz ide with working code and components; https://stackblitz.com/edit/react-4798tj

There are other solutions like React-PDF that introduce optimised components that can be rendered to pdfs. I’ve tried working with it but feel that project is still very nascent. Feel free to try that approach too.

[August 25, 2018] Update 0; Drawbacks of this approach

Many people here (and on reddit) pointed out that pdfs generated using this approach will not have its text selectable. If that is a concern, you might want to read this post by William Kwok. It walks through another library that lets you generate react components with selectable text on pdf.


If this post was helpful, don’t forget to hit that clap button.

You can also follow me on medium, on twitter or github.

I also run a slack community to help people find the best tech path and grow on their existing paths. You can request an invite by emailing me.


Like what you read? Give Shivek Khurana a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.