Serverless — PDF Generation

Jose Santos
Aug 14, 2019 · 6 min read
Image for post
Image for post


At GumGum Sports we strive to give the most value to our customers every day. Understanding our clients is fundamental, so we anonymously collect several metrics to give us a picture of how our clients interact with our products.

Based on our metrics, we found that one of the most used pages in our applications is what we call “Insights”. This page allows our clients to generate custom tables and graphs based on all the data that we aggregate for them.

Working with our clients, we understood that they were accessing this data to make customized reports to be shared with their partners. Knowing this we sought ways for our clients to easily generate all the reports they needed.

That’s how we came up with the idea of generating custom reports that our customers could quickly download, print and share with others without having to manually aggregate data and create reports on their own. One of the most standard formats for sharing reports are PDFs, and that’s how our journey begins.

In this article, I’ll guide you into generating cool-looking reports by simply calling a function in the cloud (serverless):

  • Node 10.15.x or older
  • Npm 6.9.x or older

First, we need to install serverless globally:

npm install --global serverless

Then we can clone the following boilerplate code:

git clone

This includes the setup for serverless, webpack, docker, and other goodies that will save you a ton of time.

What’s Included

First, let’s talk about the serverless.yml file, which includes the configuration for our lambda. While ours works around AWS’s services, Serverless is platform-agnostic, and the changes should be minimal if you prefer other options like Google Cloud Functions or Azure’s Serverless.

service: demo-pdf-generationprovider:
name: aws
runtime: nodejs10.x
stage: ${opt:stage, 'dev'}
role: customRole
handler: src/index.handler
- http:
path: pdf-generator
method: get
cors: true
- serverless-offline
- serverless-webpack
individually: true
webpackConfig: './webpack.config.js'
includeModules: true,
packager: 'yarn'

Our configuration is pretty standard, it is highly recommended that you use serverless-webpack since we will be using React for our PDF’s templates. Another highly recommended plugin is serverless-offline for a great local development experience.

The boilerplate also includes an example for a custom role that will allow you to upload your PDFs to AWS’s S3.

There is not much to talk about regarding webpack’s configuration. Feel free to modify the boilerplate’s configuration as you see fit.

const path = require('path');
const slsw = require('serverless-webpack');
const nodeExternals = require('webpack-node-externals');
const CopyPlugin = require('copy-webpack-plugin');
module.exports = {
stats: 'minimal',
entry: slsw.lib.entries,
mode: slsw.lib.webpack.isLocal ? 'development' : 'production',
resolve: {
extensions: ['.js', '.jsx', '.json'],
target: 'node',
optimization: { /* ... */ },
performance: { /* ... */ },
devtool: 'nosources-source-map',
externals: [nodeExternals()],
plugins: [ /* ... */ ],
module: {
rules: [
test: /\\.jsx?$/,
exclude: /node_modules/,
use: 'babel-loader',
/* ... */
output: {
libraryTarget: 'commonjs2',
path: path.join(__dirname, '.webpack'),
filename: '[name].js',
sourceMapFilename: '[file].map',

Just make sure to keep our babel-loader plugin which will allow us to transpile React’s JSX files. This is our .babelrc file to do so:

"comments": false,
"presets": [
"targets": {
"node": "10.15"
"plugins": ["source-map-support"]

Now to the more interesting bits. To generate PDFs we use react-pdf, which uses React for its layout/templates. Let's look at an example to generate a basic PDF:

import React from 'react';
import ReactPDF, { Text, Font, StyleSheet, Document, Page } from '@react-pdf/renderer';
const handler = async (event, ctx) => {
const pdfStream= await ReactPDF.renderToStream(
<Page style={styles.body}>
<Text style={styles.title}>
Hello World
return { /* ... */ };
family: 'Oswald',
/* ... */
// PDF Styles
const styles = StyleSheet.create({ /* ... */ });
export { handler };

Note how the markup seems pretty familiar if you have worked with React before. It is important that your PDF is wrapped with a <Document> component. The <Page> component is used to break your layout between multiple pages, and <Text> component is necessary to render any kind of text.

For more information, it is highly recommended that you visit the docs:

It is also worth mentioning that not all CSS properties work on react-pdf and some will behave quite differently from what they do on the web, so be warned! Here is the list of supported properties:

Now a report wouldn’t be complete without graphs. For those we use chart.js. It uses the canvas API on the browser to generate graphs. However, this ended up being more challenging than we expected. All canvases in react-pdf were rendered blank, so after tinkering for a while, we decided to transform the charts into images then include them on the PDF.

To do so we used the package chartjs-node-canvas which in turn uses node-canvas that down the road uses cairo for 2D graphics, and pango for text rendering. Understanding all this will pose crucial later on. You see, when you run your code in serverless you don’t know much about the machine that you are running on unless you do some digging.

What we found is that amazonlinux and amazonlinux2, the images that Amazon runs your functions on, are the bare minimum to run your code. With things like cairo or pango that require lots of utilities to be included on the operating system, this was a big issue.

FROM amazonlinux2:latestRUN curl --silent --location <> | bash -
RUN yum install -y nodejs zip awscli
RUN npm install -g yarn serverless
RUN mkdir /app
COPY package.json yarn.lock ./
RUN yarn --pure-lockfile
COPY . ./CMD sls deploy --stage prod

Conveniently amazon provides their Linux images on Dockerhub, so we can emulate locally what our functions will encounter once we upload them to AWS Lambda.

One final thing that we must do in order for pango to have fonts to work with (remember, your code runs in a very limited machine) is to include our fonts.

First, we must create a font.conf file that looks like this:

<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">

We recommend that you place it in the root of your functions. Then, in your function’s main file, preferably close to the top of your file. We should set an environment variable pointing to our file.

process.env.FONTCONFIG_PATH = __dirname;

That’s all the configuration that we have to make. Now you can upload your fonts to the /fonts folder. Do not forget to register them on node-canvas like this:

const { registerFont } = require('canvas');registerFont(
{ family: 'customfont' }

Note: it seems that only .tff fonts are supported by react-pdf.


If you cloned the boilerplate, it should be as simple as running the two following commands:

yarn run docker:build
yarn run docker:run

These two commands should build our docker image and run it. Do not forget to set up your AWS credentials in your machine. Usually located in the ~/.aws folder. Serverless will automatically pick them up.

Deploying to lambda requires lots of permissions, so make sure that your account is privileged enough.

If everything went well, you should have a function that uploads your PDFs to S3 and returns you a link. Like this:

With some extra work, you can achieve really impressive reports. Here is a sample of one of the reports we generate (note: some data points have been changed or removed for demonstration purposes):


While at first, all this configuration may be a bit too much, it is only necessary once. The boilerplate should have it all set for you. We found this alternative to be the best for our goals since other PDF generators simply generate images by rendering your app’s HTML.

After we implemented this feature, our clients generated more than 80 reports in less than a week. Many were surprised that we could generate PDFs on demand. We hope that this guide helped you as well to make your app that much more special.

We’re always looking for new talent! View jobs.

Follow us: Facebook | Twitter | | Linkedin | Instagram


Thoughts from the GumGum tech team

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store