Tips
Using PagedJS with Next.js
Using PagedJS with Next.js
Using PagedJS with Next.js
Create a nice PDF with Next.js and PagedJS
Set up a Next.js project
First, let's make a new Next.js project
Or you can follow the installation instructions from the official documentation.
Install the pagedjs npm module
The pagedjs library is available through a script, a command line or a npm module. We'll focus on the npm module here but feel free to go check their documentation for more solutions.
Install the pagedjs npm module.
npm install --save
Create a new page
Let's create a new page to display our PDF.
Create a new directory under the app
folder named about
and immediatly create a file called page.tsx
with the following code.
export default function About() {
return (
<div className="App">
<section className="chapter" id="about-page">
<h2 className="title" id="label-title">About</h2>
<p className="text">Lorem ipsum dolor sit amet consectetur adipiscing elit. Duis nibh tortor</p>
</section>
<section className="chapter" id="chapter1-page">
<h2 className="title">Chapter 1</h2>
<p className="text">Lorem ipsum dolor sit amet</p>
</section>
<section className="chapter" id="chapter2-page">
<h2 className="title">Chapter 2</h2>
<p className="text">consectetur adipiscing elit</p>
</section>
<section className="chapter" id="chapter3-page">
<h2 className="title">Chapter 3</h2>
<p className="text">Duis nibh tortor, pellentesque eu suscipit vel</p>
</section>
</div>
Setup PagedJS
We'll use the Previewer from pagedjs to display our page as a PDF.
First, import the Previewer from the pagedjs npm module by adding at the top of your file
import { Previewer } from 'pagedjs';
Then, add a hook effect to initialize the Previewer when the component is mounted.
useEffect(() => {
startPreview();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Don't forget to import the useEffect
hook from React.
import { useEffect } from 'react';
And to indicate that this component is a client-side component, so we can use this effect (Next.js documentation is here).
"use client";
This is what the function startPreview
looks like (pagedjs documentation is here).
const startPreview = () => {
if (isInitialized) return;
isInitialized = true;
let DOMContent = document.querySelector('.App');
let paged = new Previewer();
paged.preview(DOMContent.content).then((flow: any) => {
console.log('Rendered', flow.total, 'pages.');
});
};
We use the variable isInitialized
to make sure we don't initialize the Previewer more than once, as the component gets remounted by React in development mode. Be sure to initialize it
...
export default function About() {
let isInitialized = false; // <-- add this line
useEffect(() => {
...
To summarize, your page.tsx
file should look like this
"use client";
import { useEffect } from 'react';
import { Previewer } from 'pagedjs';
export default function About() {
let isInitialized = false;
useEffect(() => {
startPreview();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const startPreview = () => {
if (isInitialized) return;
isInitialized = true;
let DOMContent = document.querySelector('.App');
let paged = new Previewer();
paged.preview(DOMContent.content).then((flow: any) => {
console.log('Rendered', flow.total, 'pages.');
});
};
return (
<div className="App">
<section className="chapter" id="about-page">
<h2 className="title" id="label-title">About</h2>
<p className="text">Lorem ipsum dolor sit amet consectetur adipiscing elit. Duis nibh tortor</p>
</section>
<section className="chapter" id="chapter1-page">
<h2 className="title">Chapter 1</h2>
<p className="text">Lorem ipsum dolor sit amet</p>
</section>
<section className="chapter" id="chapter2-page">
<h2 className="title">Chapter 2</h2>
<p className="text">consectetur adipiscing elit</p>
</section>
<section className="chapter" id="chapter3-page">
<h2 className="title">Chapter 3</h2>
<p className="text">Duis nibh tortor, pellentesque eu suscipit vel</p>
</section>
</div>
)
}
Styling the page
Now you are ready to add style for pagedjs to create your beautiful PDF.
Start by creating a css file named about.css
in the same foler as your page.tsx
file, with the following content.
.App {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
padding: 2rem 4rem;
box-shadow: 0 0 0 1px grey;
}
h2 {
text-align: center;
color: #3c3c3c;
}
p {
text-align: center;
color: #a1a1a1;
}
This is just general styling for your page, it has nothing to do with pagedjs.
Now in this same file about.css
, add the following css to separate each chapter on a new page.
.chapter {
break-after: page;
}
Add the following to format the page
@page {
size: 8.5in 11in;
margin: 20mm 25mm;
}
And finally, add the following to add text at the top of the first page
@page:first {
@top-center { content: 'START'; }
}
Display custom content through "named strings"
What if you want to display the name of the chapter you are in at the bottom of the PDF ?
Let's add the following css to the about.css
file.
@page {
@bottom-center { content: string(title); }
}
.chapter > h2 {
string-set: title content(text);
}
The first block, is used to tell pagedjs to add content in the margin of the PDF, at the bottom center position (check all the positions available).
The second block, is used to make available the title of the chapter to pagedjs, we use the named strings feature from pagedjs to give the content of the h2 tag from every chapter into the named string "title".
Display custom content through HTML attributes
It is also possible to display content in the PDF declaring custom attributes into the HTML.
First, to every section
tag in your component, add a custom attribute data-*
where you replace *
with whatever name you want to give to your content.
For example adding a data-reference
to every chapter.
<section className="chapter" id="about-page" data-reference="001">
<h2 className="title" id="label-title">About</h2>
<p className="text">Lorem ipsum dolor sit amet consectetur adipiscing elit. Duis nibh tortor</p>
</section>
<section className="chapter" id="chapter1-page" data-reference="002">
<h2 className="title">Chapter 1</h2>
<p className="text">Lorem ipsum dolor sit amet</p>
</section>
<section className="chapter" id="chapter2-page" data-reference="003">
<h2 className="title">Chapter 2</h2>
<p className="text">consectetur adipiscing elit</p>
</section>
<section className="chapter" id="chapter3-page" data-reference="004">
<h2 className="title">Chapter 3</h2>
<p className="text">Duis nibh tortor, pellentesque eu suscipit vel</p>
</section>
Then, add the following to your about.css
.
@page {
@right-middle { content: string(ref); }
}
.chapter {
string-set: ref attr(data-reference);
}
The first block, is used to tell pagedjs where to display the content (just as before, you can check all the positions available).
The second block, is used to make available the reference to pagedjs, we use the generated text feature from pagedjs.
What's next
Be sure to check the PagedJS documentation to see all the features available and how you can use them to render beautiful PDFs !
Set up a Next.js project
First, let's make a new Next.js project
Or you can follow the installation instructions from the official documentation.
Install the pagedjs npm module
The pagedjs library is available through a script, a command line or a npm module. We'll focus on the npm module here but feel free to go check their documentation for more solutions.
Install the pagedjs npm module.
npm install --save
Create a new page
Let's create a new page to display our PDF.
Create a new directory under the app
folder named about
and immediatly create a file called page.tsx
with the following code.
export default function About() {
return (
<div className="App">
<section className="chapter" id="about-page">
<h2 className="title" id="label-title">About</h2>
<p className="text">Lorem ipsum dolor sit amet consectetur adipiscing elit. Duis nibh tortor</p>
</section>
<section className="chapter" id="chapter1-page">
<h2 className="title">Chapter 1</h2>
<p className="text">Lorem ipsum dolor sit amet</p>
</section>
<section className="chapter" id="chapter2-page">
<h2 className="title">Chapter 2</h2>
<p className="text">consectetur adipiscing elit</p>
</section>
<section className="chapter" id="chapter3-page">
<h2 className="title">Chapter 3</h2>
<p className="text">Duis nibh tortor, pellentesque eu suscipit vel</p>
</section>
</div>
Setup PagedJS
We'll use the Previewer from pagedjs to display our page as a PDF.
First, import the Previewer from the pagedjs npm module by adding at the top of your file
import { Previewer } from 'pagedjs';
Then, add a hook effect to initialize the Previewer when the component is mounted.
useEffect(() => {
startPreview();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Don't forget to import the useEffect
hook from React.
import { useEffect } from 'react';
And to indicate that this component is a client-side component, so we can use this effect (Next.js documentation is here).
"use client";
This is what the function startPreview
looks like (pagedjs documentation is here).
const startPreview = () => {
if (isInitialized) return;
isInitialized = true;
let DOMContent = document.querySelector('.App');
let paged = new Previewer();
paged.preview(DOMContent.content).then((flow: any) => {
console.log('Rendered', flow.total, 'pages.');
});
};
We use the variable isInitialized
to make sure we don't initialize the Previewer more than once, as the component gets remounted by React in development mode. Be sure to initialize it
...
export default function About() {
let isInitialized = false; // <-- add this line
useEffect(() => {
...
To summarize, your page.tsx
file should look like this
"use client";
import { useEffect } from 'react';
import { Previewer } from 'pagedjs';
export default function About() {
let isInitialized = false;
useEffect(() => {
startPreview();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const startPreview = () => {
if (isInitialized) return;
isInitialized = true;
let DOMContent = document.querySelector('.App');
let paged = new Previewer();
paged.preview(DOMContent.content).then((flow: any) => {
console.log('Rendered', flow.total, 'pages.');
});
};
return (
<div className="App">
<section className="chapter" id="about-page">
<h2 className="title" id="label-title">About</h2>
<p className="text">Lorem ipsum dolor sit amet consectetur adipiscing elit. Duis nibh tortor</p>
</section>
<section className="chapter" id="chapter1-page">
<h2 className="title">Chapter 1</h2>
<p className="text">Lorem ipsum dolor sit amet</p>
</section>
<section className="chapter" id="chapter2-page">
<h2 className="title">Chapter 2</h2>
<p className="text">consectetur adipiscing elit</p>
</section>
<section className="chapter" id="chapter3-page">
<h2 className="title">Chapter 3</h2>
<p className="text">Duis nibh tortor, pellentesque eu suscipit vel</p>
</section>
</div>
)
}
Styling the page
Now you are ready to add style for pagedjs to create your beautiful PDF.
Start by creating a css file named about.css
in the same foler as your page.tsx
file, with the following content.
.App {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
padding: 2rem 4rem;
box-shadow: 0 0 0 1px grey;
}
h2 {
text-align: center;
color: #3c3c3c;
}
p {
text-align: center;
color: #a1a1a1;
}
This is just general styling for your page, it has nothing to do with pagedjs.
Now in this same file about.css
, add the following css to separate each chapter on a new page.
.chapter {
break-after: page;
}
Add the following to format the page
@page {
size: 8.5in 11in;
margin: 20mm 25mm;
}
And finally, add the following to add text at the top of the first page
@page:first {
@top-center { content: 'START'; }
}
Display custom content through "named strings"
What if you want to display the name of the chapter you are in at the bottom of the PDF ?
Let's add the following css to the about.css
file.
@page {
@bottom-center { content: string(title); }
}
.chapter > h2 {
string-set: title content(text);
}
The first block, is used to tell pagedjs to add content in the margin of the PDF, at the bottom center position (check all the positions available).
The second block, is used to make available the title of the chapter to pagedjs, we use the named strings feature from pagedjs to give the content of the h2 tag from every chapter into the named string "title".
Display custom content through HTML attributes
It is also possible to display content in the PDF declaring custom attributes into the HTML.
First, to every section
tag in your component, add a custom attribute data-*
where you replace *
with whatever name you want to give to your content.
For example adding a data-reference
to every chapter.
<section className="chapter" id="about-page" data-reference="001">
<h2 className="title" id="label-title">About</h2>
<p className="text">Lorem ipsum dolor sit amet consectetur adipiscing elit. Duis nibh tortor</p>
</section>
<section className="chapter" id="chapter1-page" data-reference="002">
<h2 className="title">Chapter 1</h2>
<p className="text">Lorem ipsum dolor sit amet</p>
</section>
<section className="chapter" id="chapter2-page" data-reference="003">
<h2 className="title">Chapter 2</h2>
<p className="text">consectetur adipiscing elit</p>
</section>
<section className="chapter" id="chapter3-page" data-reference="004">
<h2 className="title">Chapter 3</h2>
<p className="text">Duis nibh tortor, pellentesque eu suscipit vel</p>
</section>
Then, add the following to your about.css
.
@page {
@right-middle { content: string(ref); }
}
.chapter {
string-set: ref attr(data-reference);
}
The first block, is used to tell pagedjs where to display the content (just as before, you can check all the positions available).
The second block, is used to make available the reference to pagedjs, we use the generated text feature from pagedjs.
What's next
Be sure to check the PagedJS documentation to see all the features available and how you can use them to render beautiful PDFs !
By Frédéric Llorca
January 8, 2024