Google's Puppeteer is a Node API that allows you to control an instance of Chrome using a Node service. Leveraging this technology, we are easily able to use Node to generate PDFs using any Javascript framework we wish.

In this post, we will go over how to use a React PDF Generator project alongside a Node service to generate styled PDFs.

This structure is the backbone of how PDFTron's web-to-pdf works. If you are looking for the best PDF generator using web technology, we recommend checking it out! It comes with many features that we do not cover in this article, such as converting a remote PDF to a URL, pagination, dynamic content, and much more.

React is not required!
Please note that the React PDF Renderer is not required for web-to-pdf to work. It supports all frameworks, and even vanilla JS/HTML/CSS.

Step 1 - Prepare Project for the React PDF Creator

In this tutorial, we will use create-react-app to quickly set up our front end dev environment to use the React PDF generator and NPM. Navigate to an empty directory and install create-react-app by running

npm i create-react-app

Now let's generate our project using the following command:

npx create-react-app react-to-pdf

Once the project is generated, let's start the local server by running

cd react-to-pdf
npm run start

Your default browser will open automatically and will display the default create-react-app screen!

Step 2 - Create the UI

Now we will clean up the project and get our UI set up. First, start by deleting all the code in src/App.js, except the outer div. Let's also remove the logo.svg import. App.js should now look like this:

import React, { Component } from 'react';
import './App.css';

class App extends Component {
  render() {
    return (
      <div className="App">

      </div>
    );
  }
}

export default App;

Let's also delete all the unused files that come with the template. Delete src/App.test.js and src/logo.svg.

To make sure our UI will fit inside the bounds of a PDF viewer, let's set up our UI to roughly match the default size of a PDF. Inside App.js, let's create a new div that will hold the contents of our native PDF. App.js should look something like this:

import React, { Component } from 'react';
import './App.css';

class App extends Component {
  render() {
    return (
      <div className="App">
        <div className='pdf'>
        
        </div>
      </div>
    );
  }
}

export default App;

Now, we can set .pdf to match the size of a PDF. In src/App.css, delete all the existing CSS and replace it with:

html, body, #root, .App {
  width: 100%;
  height: 100%;
  background-color: #d1d1d1
}

/* 612px x 792px is our default size of a PDF */
.pdf {
  width: 612px;
  height: 792px;
  background-color: white;
  margin: 0 auto;
}

If you check out http://localhost:3000/, you should see a grey background with a white 'pdf' in the middle!

Now let's put some content inside our PDF. For this example, we are going to use some fake data, but you can do whatever you want here (fetch data from a remote server, for example).

In the render function of App.js, let's add some fake data like so:

import React, { Component } from 'react';
import './App.css';

class App extends Component {
  render() {

    const data = [
      { firstName: "John", lastName: "Smath" },
      { firstName: "Paul", lastName: "Smeth" },
      { firstName: "Cody", lastName: "Smith" },
      { firstName: "Jordan", lastName: "Smoth" },
      { firstName: "Jim", lastName: "Smuth" },
    ]

    return (
      <div className="App">
        <div className='pdf'>
        
        </div>
      </div>
    );
  }
}

export default App;

Now, let's use a React PDF Renderer to render this data. Create the file src/components/User/User.js and set up a component to render a user. The component should accept a user object as a prop. The file might look something like this:

import React from 'react';

export default ({ user }) => {
  return (
    <div className='user' style={{ display: 'flex', justifyContent: 'center'}}>
      <p>{user.firstName} {user.lastName}</p>
    </div>
  )
}

Now, back in App.js, let's import this component and use it to render our list of users.

import React, { Component } from 'react';
import './App.css';
import User from './components/User/User';

class App extends Component {
  render() {

    const data = [
      { firstName: "John", lastName: "Smath" },
      { firstName: "Paul", lastName: "Smeth" },
      { firstName: "Cody", lastName: "Smith" },
      { firstName: "Jordan", lastName: "Smoth" },
      { firstName: "Jim", lastName: "Smuth" },
    ]

    return (
      <div className="App">
        <div className='pdf'>
          {
            data.map(user => <User user={user} key={user.lastName}/>)
          }
        </div>
      </div>
    );
  }
}

export default App;

If you check out http://localhost:3000/ now, you should see a simple list of users rendered in your "pdf".

Go crazy with your UI!
We are going to leave the UI as is, but you can do anything you want at this point to make the PDF look the way you want. Feel free to go crazy! No limitations here.

It is important to note that what you see is what you get. When we generate the PDF, the PDF will look exactly like the content displayed at http://localhost:3000/. You can use images, canvas, 3rd party libraries, and whatever PDF editor you want!

Step 3 - Create the Node Service to Generate PDF

Our Node service will need a few extra dependencies (Puppeteer and Babel-node). Install these with the following command.

npm i puppeteer @babel/core @babel/node --save-dev

Now let's create our service. From the root of the project, make the file scripts/generate-pdf.js. This is the file that the service will live in.

The code for the service is actually pretty simple. Let's start creating a new instance of Puppeteer and navigating it to our local server.

Note
The local server we set up in parts 1 & 2 needs to be running! This means that http://localhost:3000/ needs to be serving your React project.

generate-pdf.js should look something like this:

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('http://localhost:3000/', {waitUntil: 'networkidle2'});
})()

This code basically creates an in-memory instance of Chrome, creates a new tab, and navigates the tab to http://localhost:3000/. If you are unfamiliar with async/await syntax, I recommend learning about it.

Now, we are ready to generate the PDF! Luckily Puppeteer provides an amazing API for this.

We are going to add some code that tells Chrome to emulate a screen when it's generating the PDF, and also a call to page.pdf() to generate the PDF. We'll pass in a couple options to make the PDF look the way we want.

Update generate-pdf.js to look like this:

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('http://localhost:3000/');
  await page.emulateMedia('screen');
  await page.pdf({
    path: './react.pdf', // path (relative to CWD) to save the PDF to.
    printBackground: true,// print background colors
    width: '612px', // match the css width and height we set for our PDF
    height: '792px',
  });
  await browser.close();
})()

You can also view additional page.pdf(options).

And that's it! Our service is done and we are ready to generate a PDF.

Step 4 - Execute the Service

The last step will be actually executing our script. In the root of the project there should be a package.json file. Let's write a script to execute our file here. Add the following line to the scripts section in package.json.

"scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
+   "generate": "npx babel-node scripts/generate-pdf.js"
  },

This script uses babel-node to transpile our JS, and then executes the transpiled source code using Node.

Now, in the root of the project, making sure http://localhost:3000 is still running, run the following command.

npm run generate

When the script is finished, you should find a react.pdf at the root of the project that matches your React UI.

React PDF generator

Conclusion

Google's Puppeteer project has created a ton of opportunities in the PDF space. Using Node JS, Puppeteer, and HTML to generate PDFs, along with the CSS, we can now easily generate PDFs that are all geared towards UI in the first place!

For more information, check out our video on how to generate PDF with PDFTron PDFNet Node.js SDK.

If you would like to view and annotate your generated PDFs in your website, consider downloading a free trial of Webviewer, PDFTron's JavaScript PDF viewer that allows you to open , edit , and annotate PDFs right in the browser (and much more!).

Also, check out our How to Build a PDF Viewer in React Native blog post.

If you have any questions regarding WebViewer or, this article, or anything else, please feel free to contact us and we will be happy to help.