DEV Community

Layton Whiteley
Layton Whiteley

Posted on

React and Puppeteer: Pdf generation (project setup)

Summary

This is an experimentation detailing how to generate pdf documents using a server and React as the template engine.

Why

I have used @react-pdf/renderer in the past and this aims to solve a few issues I had with it

Issues

  • Limited set of components to work with
  • Maintenance has slowed down, therefore, the future outlook doesnt look bright for unresolved issues
  • Currently the library does not work with react 18 so this makes it difficult to upgrade an application to react 18

Goals

  • Decouple pdf generation from the client application
  • Have a simple server process to generate PDF documents
  • Keep using React as a familiar library for UI development
  • Only critical coding steps will be detailed, please check the final source code if something is missing in the steps

Why not just do it in the user's browser/client?

  • This is meant for use cases where there needs to be a repeatable process
  • In this scenario, simply creating print styles doesnt work as the view needed for the pdf is not necessarily what is being viewed in the client
  • user could be using a mobile application

Note: The goals, libraries and specific data flows used for the experiment are purely subjective as not every use case will need to do all these steps. Mainly focus on how the pdf is generated.

Technology

  • React: use as a template engine. any other templating engine can be used
    • chakra ui: used as a ui library. not needed but has a lot of simple to use components for a demo. alternatives ca be used
    • storybook: used as a ui development assistant library. not needed but good to use to develop the view/pdf visually
  • Puppeteer: used as an in place html renderer and orchestrator: alternatives can also be used
  • nx: used for project management. We will create multiple projects in the same repository. Nx makes this easier to link up all projects. Alternatives can be used

TL;DR;

view the full project on github:
https://github.com/lwhiteley/pdf-generation-experiment

git clone https://github.com/lwhiteley/pdf-generation-experiment
cd pdf-generation-experiment
pnpm i
pnpm nx run-many --target=serve --projects=pdf-server,ui-app
Enter fullscreen mode Exit fullscreen mode

After running these commands, you can then use the app experience the full solution.


Setup

Create project + Install dependencies

npx create-nx-workspace --pm pnpm pdf-generation
# ? Enable distributed caching to make your CI faste: No

cd pdf-generation
pnpm add -D @nrwl/react @nrwl/nest @nrwl/storybook @nrwl/node
pnpm add pdf-lib puppeteer utf-8-validate sharp
pnpm add @chakra-ui/react @emotion/react @emotion/styled framer-motion @chakra-ui/icons @chakra-ui/styled-system use-debounce axios
pnpm add -D @chakra-ui/storybook-addon
pnpm add interweave interweave-ssr

Enter fullscreen mode Exit fullscreen mode

Generate the projects we will work with

pnpm nx g @nrwl/react:application ui-app
  # Choose: emotion
  # use router: Yes

pnpm nx g @nrwl/nest:application pdf-server

pnpm nx g @nrwl/node:library constants

pnpm nx g @nrwl/react:library pdf-doc

pnpm nx g @nrwl/storybook:configuration pdf-doc
  # Choose: @storybook/react 
Enter fullscreen mode Exit fullscreen mode

Notes:

Configure pdf-server for React and deployment

  • Move @babel/core to dependencies in package.json
  • in apps/pdf-server/project.json
    • set targets.build.options.generatePackageJson: true
    • essentially adding: "generatePackageJson": true in the specified location of the json file
  • Copy .babelrc file from ui-app and place it into the pdf-server root folder
  • Add "jsx": "react-jsx" in
    • apps/pdf-server/tsconfig.app.json
    • apps/pdf-server/tsconfig.spec.json

Create a file at the same level of apps/pdf-server/src/main.ts

// file: dependencies.register.ts

/**
 * We import dependencies that are missed by nx auto package.json creation
 */

import '@babel/core';
import '@emotion/styled';
import '@chakra-ui/styled-system';
import 'utf-8-validate';
import { polyfill } from 'interweave-ssr';

// view this file for environment config
import { environment } from './environments/environment';

polyfill();

// this could be moved to a helper library
export const createDirectory = (directory: string) => {
  if (existsSync(directory)) return false;
  mkdirSync(directory, { recursive: true });
  return true;
};

createDirectory(environment.tmpFolder);

Enter fullscreen mode Exit fullscreen mode

Then import it in main.ts

import './dependencies.register.ts'
Enter fullscreen mode Exit fullscreen mode

Create nestjs pdf module

pnpm nx g @nrwl/nest:module pdf --project=pdf-server
pnpm nx g @nrwl/nest:controller pdf --project=pdf-server
pnpm nx g @nrwl/nest:service pdf --project=pdf-server
Enter fullscreen mode Exit fullscreen mode

Proxy the pdf-server to ui-app

Create a file apps/ui-app/proxy.config.json

{
  "/api": {
    "target": "http://localhost:3333/api",
    "pathRewrite": { "^/api": "" },
    "secure": false,
    "logLevel": "debug"
  }
}
Enter fullscreen mode Exit fullscreen mode

then in apps/ui-app/project.json

add the following to targets.serve.configurations.development

"proxyConfig": "apps/ui-app/proxy.config.json",
"open": true
Enter fullscreen mode Exit fullscreen mode

Now that we've gotten the setup done its time to write some code

Top comments (0)