DEV Community

Cover image for Make a PDF with React & Make.cm and avoid the pain of ongoing service management [Part 1/2]
James Lee for Make.cm

Posted on • Edited on

Make a PDF with React & Make.cm and avoid the pain of ongoing service management [Part 1/2]

Looking for Part 2?
Make.cm

Make a PDF with React & Make.cm and avoid the pain of ongoing service management [Part 2/2]

James Lee for Make.cm ・ Mar 25 '21

#react #pdf #javascript #tutorial

What we're building

Today we're going to be building a PDF certificate generator. This guide will be split over two parts.

  • Part 1: Getting started, building the React certificate template and importing it into Make.
  • Part 2: Building the certificate generator application and cleaning it up

When you put it altogether this is what we're cooking πŸ‘¨β€πŸ³. Check it out

preview of the certificate app

Resources

GitHub logo makecm / certificate-app

A simple react application to generate a PDF certificate using Make.cm

GitHub logo makecm / certificate-template

A simple certificate template that can be forked and imported into Make.cm


Why are we building a PDF generator?

Read more

There will come a point in time where a PDF generation service is required functionality for your application. It's a fact of life for anything from an invoice or receipt, to a ticket, or even something professionally printable like a business card or event name badge.

Building a PDF generation service is not a new workload. On the contrary - it's a well documented procedure at this point, especially since the rise in popularity of Headless Chrome services over the last few years.

But with most, the work is still on you as a developer to create a service that scales to meet demand, produces quality output - every time and is flexible enough to meet new capability as it arises.

However these services all fall in the trap of:

  • Ending up being so fit for purpose - as needs and products pivot, the service can't sustain change.
  • They are never as easy as you hoped and what you end up building isn't what you set out to build.
  • What you should be focussed on building and crafting (your application), becomes secondary to the function of it (generating a PDF).

From Andy Fitzsimon's article, Did you ever make, Make

Maintaining and managing a PDF service, especially an ageing one, is a large investment. But it doesn't have to be, nor does it have to take away from what you want to build.

That's where we come in at Make.cm. Instead of you having to maintain and manage your services and dependencies, let us do it for you. And while we're at it we'll do it in a completely scalable and severless environment so that every PDF will generate as quickly as possible and every PDF will be of the same quality. And by the way we haven't even talked about post processing functionality after the PDF has been generated ... we do that as well.


1. Getting Started

We're going to be creating two react apps with Create React App (CRA). One for our template that we'll import into Make and then the other react app will be our front end application (we'll go through building our app in Part 2).

To get started let's go ahead and create our two react apps.

$ npx create-react-app certificate-template
$ npx create-react-app certificate-app
Enter fullscreen mode Exit fullscreen mode

CRA gives us a lot of lovely functionality out of the box but for these simple apps we just don't need all of that goodness. For sanity's sake we can strip out the following files in both of your newly created apps.

// certificate-app & certificate-template

/node_modules
/public
/src
  App.css
  App.js
  App.test.js πŸ—‘
  index.css πŸ—‘
  index.js
  logo.svg πŸ—‘
  reportWebVitals.js πŸ—‘
  setupTests.js πŸ—‘
.gitignore
package.json
README.md
yarn.lock
Enter fullscreen mode Exit fullscreen mode

After deleting those files, you'll have to clean up some broken imports in your App.js and index.js

The last thing I would suggest doing is installing a really simple CSS reset into both of your react apps. For me I really like minireset.css

$ cd certificate-template
$ yarn add minireset.css
$ ..
$ cd certificate-app
$ yarn add minireset.css
Enter fullscreen mode Exit fullscreen mode

Once minireset.css has been installed in both apps you can import it the App.js on both applications with the following.

// App.js

import 'minireset.css';
import './App.css';

function App() {
  return <div className="App">{/* OUR APP CODE */}</div>;
}

export default App;
Enter fullscreen mode Exit fullscreen mode

2. Creating our template

Let's spin up our server for certificate-template

$ yarn start
Enter fullscreen mode Exit fullscreen mode

Just as a reminder for this template we're building a certificate template that will need to accept the following:

  • Recipient name (name - string)
  • Completed course name (course - string)
  • Today's date (date - string)

And this is what our lovely certificate will look like.

preview of the certificate template

If you want to cheat you can fork this repo to your Github and skip to Importing the template into Make.cm.

Adding our structure

In our App.js file let's setup the following structure.

// App.js

import 'minireset.css';
import './App.css';

function App() {
  return (
    <div className="App">
      <Icon />
      <p className="byline">Certificate of completion</p>

      <div className="content">
        <p>Awarded to</p>
        <h1>Name Surname</h1>
        <p>for completing:</p>
        <h2>Creating PDFs with React & Make.cm</h2>
      </div>

      <p className="date">
        Issued on <span className="bold">March 15 2021</span>
      </p>
    </div>
  );
}

const Icon = () => (
  <svg
    width="99"
    height="139"
    viewBox="0 0 99 139"
    fill="none"
    xmlns="http://www.w3.org/2000/svg"
  >
    <path d="M0 0H99V138.406L52.1955 118.324L0 138.406V0Z" fill="white" />
    <path
      d="M25.4912 83.2515C25.4912 79.4116 27.0222 75.7289 29.7474 73.0137C32.4727 70.2985 36.1689 68.7731 40.0229 68.7731C43.877 68.7731 47.5732 70.2985 50.2984 73.0137C53.0236 75.7289 54.5546 79.4116 54.5546 83.2515M40.0229 59.724C40.0229 55.8841 41.5539 52.2014 44.2791 49.4862C47.0044 46.7709 50.7006 45.2455 54.5546 45.2455C58.4087 45.2455 62.1049 46.7709 64.8301 49.4862C67.5553 52.2014 69.0863 55.8841 69.0863 59.724V83.2515"
      stroke="#0379FF"
      strokeWidth="10.6193"
    />
  </svg>
);

export default App;
Enter fullscreen mode Exit fullscreen mode

Adding our styles

Let's turn the lights on. Copy the following styles and paste them into the App.css, replacing what was in there.

/* App.css */

@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500&family=Poppins:wght@800&display=swap');

:root {
  --blue: #0379ff;
  --light-blue: #9ac9ff;
  --dark-blue: #0261cc;
  --white: #fff;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: 'IBM Plex Sans', sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-size: 20px;
}

.App {
  width: 100vw;
  height: 100vh;
  position: relative;
  overflow: hidden;
  color: var(--light-blue);
  background-color: var(--blue);
  background-image: url('data:image/svg+xml;utf8,<svg width="55" height="45" viewBox="0 0 55 45" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M5.49121 44.2515C5.49121 40.4116 7.02223 36.7289 9.74745 34.0137C12.4727 31.2985 16.1689 29.7731 20.0229 29.7731C23.877 29.7731 27.5732 31.2985 30.2984 34.0137C33.0236 36.7289 34.5546 40.4116 34.5546 44.2515M20.0229 20.724C20.0229 16.8841 21.5539 13.2014 24.2791 10.4862C27.0044 7.77095 30.7006 6.24554 34.5546 6.24554C38.4087 6.24554 42.1049 7.77095 44.8301 10.4862C47.5553 13.2014 49.0863 16.8841 49.0863 20.724V44.2515" stroke="%230261CC50" stroke-width="11"/></svg>');
  background-size: 160%;
  background-position: 90% 150%;
  background-repeat: no-repeat;
  padding: 2.5rem;
}

svg {
  position: absolute;
  top: 0;
}

.content {
  position: absolute;
  top: 12rem;
  right: 2.5rem;
  width: 65%;
}

.content * {
  margin-bottom: 1rem;
}

.content h1 {
  font-family: 'Poppins', sans-serif;
  color: var(--white);
  font-size: 3rem !important;
  line-height: 1;
  margin-bottom: 2rem;
}

.content h2 {
  font-size: 2rem !important;
  font-weight: 500;
  line-height: 1;
}

.byline {
  position: absolute;
  right: 2.5rem;
}

.date {
  position: absolute;
  bottom: 2.5rem;
  font-size: 0.75rem;
}

.bold {
  font-weight: 500;
}
Enter fullscreen mode Exit fullscreen mode

So with those styles in there your certificate should look something like this.

preview of what the template looks like

If you're OCD like me, you can simulate the export size that we'll be passing to Make by cracking open the Dev Tools (I'm on Chrome so this may differ slightly for other browsers) and clicking on the responsive test tool and popping in 595 x 842, which are the pixel dimensions for an A4 page.

preview of what the template looks like

😍 BEAUTIFUL 😍

Adding our functionality

With the Make API you can send custom data to your template before generation. So let's prep our template to accept the custom data we want to send it from our application.

As a refresher, this is what we want our template to handle:

  • Recipient name (name - string)
  • Completed course name (course - string)
  • Today's date (date - string)

When sending custom data to a template Make creates a custom window object called templateProps that your template can then access.

To access this object in our react template we can add the following to our index.js and spread these window.templateProps onto our App.

//index.js

ReactDOM.render(
  <React.StrictMode>
+    <App {...window.templateProps} />
  </React.StrictMode>,
  document.getElementById('root')
);
Enter fullscreen mode Exit fullscreen mode

Once we do that it's as simple as de-structuring the props that we expect to receive from Make in our App.js and voila we can now accept custom data in our template.

// App.js

function App({ name, course, date }) {
  return (
    <div className="App">
      <Icon />
      <p className="byline">Certificate of completion</p>

      <div className="content">
        <p>Awarded to</p>
        <h1>{name}</h1>
        <p>for completing:</p>
        <h2>{course}</h2>
      </div>

      {date && (
        <p className="date">
          Issued on <span className="bold">{date}</span>
        </p>
      )}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

However on our local environment because we have no concept of the templateProps object, we've got no data!

We can however use defaultProps to simulate what Make would send our template.

// App.js

App.defaultProps = {
  name: 'James Lee',
  course: 'Creating PDFs with React & Make.cm',
  date: 'March 15 2021',
};
Enter fullscreen mode Exit fullscreen mode

3. Prepping & pushing to Github

Now that we've got our template we need to import it into Make.

At the moment Make does not have an application build pipeline. To circumvent this you can build your files locally and push its build folder to Github for import into Make.

To do this successfully we need to do 3 things to our react template before pushing to Github:

  1. Update the build path
  2. Update the .gitignore
  3. Build our template

Updating the build path

By default when you build a react app, CRA assumes that it will be hosted at the the server root. However in our case our template cannot be hosted at the root when imported into Make.

To allow for correct importing you can add the following to your package.json to serve all assets relative to the root.

//package.json

"homepage": "./",
Enter fullscreen mode Exit fullscreen mode

Updating the .gitignore

By default git will ignore the build folder, but we need to make special allowances so that we can push up the build folder to Github.

To do so, just remove or comment out the /build line in your .gitignore.

# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# production
# /build

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*
Enter fullscreen mode Exit fullscreen mode

Build it!

$ yarn build
Enter fullscreen mode Exit fullscreen mode

Pushing to a Github repo

Once we've done that create a new blank repository on Github and push up your certificate-template repository.

Remember to NOT initialize anything in the repository as we're about to send up everything from our local environment.

setting up repo in Github

$ git add .
$ git commit -m "initial commit"
$ git branch -M main
$ git remote add origin git@github.com:[your-username]/certificate-template.git
$ git push -u origin main
Enter fullscreen mode Exit fullscreen mode

Your template code (including the build directory) should be in your new Github repo.

setting up repo in Github

4. Importing our template into Make.cm

Now that we've got out template in Github lets finalize our import.

Jump over to app.make.cm and click Import Template

importing template into Make

Choose the repository we just created

choosing the github repository to import

Don't see your repo?

Read more

If you don't see your repo in the list, scroll to the bottom of the page and look out for the Alert (looks like the below) and click the Github button at the bottom of the page to give Make.cm the correct permissions to access that repo (in my case I needed to do this).

UI alert telling people to connect their repo to Make

Provide your template with a name and the root directory to your build path which in our case will be /build.

modal defining the settings for the imported template

Import it! πŸš€

dashboard for newly imported template

Testing it!

Now that we've imported our template, we can test it via the API playground. You can paste this into the playground and hit Send Test Request.

"size": "A4",
"format": "pdf",
"data": {
  "name": "[Your Name]",
  "course": "Importing templates into Make",
  "date": "Right now"
}
Enter fullscreen mode Exit fullscreen mode

api playground

Clicking the resultUrl link will open the generated PDF, which should look something like this!

preview of generated PDF certificate

Nice one! You've just generated your first PDF out of Make. πŸŽ‰πŸŽ‰


Concluding Part 1

Give yourself a pat on the back. You've just created your first Make template and generated your very own PDF out the other side.

In Part 2 I'll show you how you can use your new Make template endpoint in your very own certificate generator app. Grab a coffee, snack or whatever you need to do and when you're ready, dive into Part 2.

Top comments (2)

Collapse
 
onlyphantom profile image
Samuel Chan

This is helpful! Thank you!

How does Make.cm work with visualization-heavy PDF (SVG mostly, but I meant it as a more general question) in your experience?

Collapse
 
jamesrplee profile image
James Lee

Samuel, I'm glad you liked it πŸ™

Data vis works great using SVG - and the output in PDF stays vector most of the time which means great file sizes even with lots of data. Most of the d3 based charting tools work a treat by default. Chartjs works fine too but really shines when you use canvas2svg so there's no bitmaps.

If you can generate your complex visualisation in under 30s then it can be handled synchronously (could be up to 5 pages of data-heavy D3 - hard to guess without knowing your workload but 30s is huge).

For even more complex work, say assembling very large and detailed vector tile sets from OpenStreetmaps via Mapbox you'll likely need to use our async functionality. This means you'll have more time to produce exactly what you need.

Give it a try and I'm sure you'll love the output πŸ˜€