React-PDF with Next.js
A few months ago, I was asked at work to create a PDF in React/Next. Why? Because the project was in Next.js, and the PDF had to be created dynamically after purchase, taking a unique number from the backend (proof of purchase). The task seemed simple; after a short research, I chose the React-PDF library. However, it quickly became clear that there were problems. If you happen to encounter similar ones, this post should help you deal with them.
Installation
Due to the fact that React-PDF is incompatible with newer versions of React.js, it is necessary to add --legacy-peer-deps
npm install @react-pdf/renderer --save --legacy-peer-deps
The elements we use to build our PDF document are Document, Page, View (equivalent to a div), Text, Image. We import them:
import { Document, Page, View, Text, Image, PDFViewer, StyleSheet, Font } from '@react-pdf/renderer';
These are the only building blocks for our PDF.
Fonts that we want to use must be "registered" as follows:
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 structure of our document is as follows:
const PDF = () => {
return (
<Document>
<Page>
<View>
{/* Your content here */}
</View>
</Page>
</Document>
);
};
Of course, as I mentioned earlier, View here is equivalent to a div, and just like with a div, you use it here – you create as many Views as you need, nesting some within others or not.
CSS
We write CSS in two ways.
First: "inline":
<View style={{ display: 'flex', flexDirection: 'row', justifyContent: 'center' }}></View>
You might be surprised by the notation flexDirection: 'row'. Yes, I know that <code>row</code> is the default property for flex-direction, but from my experience, if we omit this notation, React-PDF causes problems.
The second way, probably a bit cleaner:
const styles = StyleSheet.create({
imageWrapper: {
marginLeft: 20
}
});
<View style={styles.imageWrapper}></View>
PDF on the Page
To render the PDF on a page, from the component where we create the PDF, we export the following function:
const PDFView = () => {
return (
<PDFViewer>
<PDF />
</PDFViewer>
);
};
export default PDFView;
Next, we import this function in the page file where the PDF is to be displayed, but – NOTE: IMPORTANT, if we are using Next.js – we must do it in the following way:
const InvoicePDF = dynamic(() => import('./pdf'), {
ssr: false,
});
Not forgetting to add the 'use client' directive in the first line.
The point is that Next.js renders pages on the server side by default, and our PDF needs to be rendered on the client side, and we need to take care of that.
Then you 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 – instead of or in addition to displaying the entire PDF – to insert a button to download it, in Next.js you will have to use a similar notation to import the PDF:
const PDFDownloadLink = dynamic(
() => import('@react-pdf/renderer').then(mod => mod.PDFDownloadLink),
{ ssr: false }
);
And then wrap it, for example, a button as follows:
<PDFDownloadLink
document={<PDF locale={locale} />}
fileName={`DominikFrackowiak_CV_${locale}.pdf`}
>
<button className={className}>
{handleTranslation(locale)}
</button>
</PDFDownloadLink>
Voila!