Frontend Developer

React-PDF with Next.js

← Back to projects

React-PDF with Next.js

A few months ago, I was asked at work to create a PDF in a React and Next.js project. The file had to be generated dynamically after purchase and include a unique number from the backend as proof of purchase. The task seemed simple: after a short research session, I chose @react-pdf/renderer. Then the first problems appeared. If you run into similar ones, this post should help you get through the setup.

Installation

Install the package:

npm install @react-pdf/renderer

If npm reports peer dependency conflicts in your project, install it with --legacy-peer-deps:

npm install @react-pdf/renderer --save --legacy-peer-deps

The basic elements used to build a PDF document are Document, Page, View, Text, and Image. You can also import helpers such as PDFViewer, PDFDownloadLink, StyleSheet, and Font:

import {
  Document,
  Page,
  View,
  Text,
  Image,
  PDFViewer,
  StyleSheet,
  Font,
} from '@react-pdf/renderer';

These are the building blocks of the PDF. View works similarly to a div, but remember that React-PDF does not render regular HTML elements inside the document.

Fonts you want to use in the PDF must be registered first:

Font.register({
  family: 'Roboto',
  fonts: [
    { src: '/assets/fonts/Roboto-Regular.ttf', fontWeight: 400 },
    { src: '/assets/fonts/Roboto-Medium.ttf', fontWeight: 500 },
    { src: '/assets/fonts/Roboto-Bold.ttf', fontWeight: 700 },
  ],
});

Document Structure

The document structure can look like this:

const PDF = () => {
  return (
    <Document>
      <Page>
        <View>
          <Text>Your content here</Text>
        </View>
      </Page>
    </Document>
  );
};

As mentioned earlier, View is the equivalent of a container. You can create as many View elements as you need and nest them depending on your layout.

Styling

You can style elements inline:

<View style={{ display: 'flex', flexDirection: 'row', justifyContent: 'center' }}>
  <Text>Content</Text>
</View>

It is worth setting flexDirection explicitly. In browser CSS, row is the default for flex-direction, but React-PDF layout behaves differently and explicit values make the result more predictable.

The second, cleaner option is StyleSheet.create:

const styles = StyleSheet.create({
  imageWrapper: {
    marginLeft: 20,
  },
});

<View style={styles.imageWrapper}>
  <Image src="/assets/example.png" />
</View>;

PDF on the Page

To display the PDF on a page, export a component with PDFViewer from the file where you create the PDF:

const PDFView = () => {
  return (
    <PDFViewer>
      <PDF />
    </PDFViewer>
  );
};

export default PDFView;

Then import that component dynamically on the page where the PDF should be displayed:

'use client';

import dynamic from 'next/dynamic';

const InvoicePDF = dynamic(() => import('./pdf'), {
  ssr: false,
});

The ssr: false option is important in Next.js. React-PDF needs browser APIs, so the viewer should be rendered on the client side.

You can then use InvoicePDF like any other React component:

export default function PDFPage() {
  return (
    <div className={styles.pdfWrapper}>
      <InvoicePDF locale={locale} />
    </div>
  );
}

Download Button for the PDF

If you want to add a download button instead of, or next to, the PDF preview, import PDFDownloadLink dynamically as well:

const PDFDownloadLink = dynamic(
  () => import('@react-pdf/renderer').then((mod) => mod.PDFDownloadLink),
  { ssr: false }
);

Then wrap your button with PDFDownloadLink:

<PDFDownloadLink
  document={<PDF locale={locale} />}
  fileName={`DominikFrackowiak_CV_${locale}.pdf`}
>
  <button className={className}>{handleTranslation(locale)}</button>
</PDFDownloadLink>

And that’s it.