Generate PDF using nextJS , html2pdf.js , Antd Design, and styled component

Bijay Kr. Budhathoki
readytowork, Inc.
Published in
4 min readFeb 6, 2023
thumbnail/icon for nextjs+pdf

In this article, we are going to learn how to create a PDF using nextJS and html2pdf.

We are also going to use antd design for our table layout and styled-components for the style.

First and foremost let’s create a brand new nextJs application by running. Notice I’m using pnpm for the package manager. You can use any other package manager such as yarn,npm

pnpm create next-app --typescript pdf-with-nextjs

Now let’s install other required dependencies

pnpm i antd styled-components

First, let’s create a simple table with some user data in it. For the sake of simplicity, I will write all code in one file. You can separate as you like.

Go to page/index.tsx

import Head from "next/head";
import Image from "next/image";
import { Inter } from "@next/font/google";
import { Button, Table, Typography } from "antd";
const inter = Inter({ subsets: ["latin"] });
import { Wrapper } from "./app.style";
export default function Home() {

const columns = [
{
title: "Name",
dataIndex: "name",
key: "name",
},
{
title: "Age",
dataIndex: "age",
key: "age",
},
{
title: "Address",
dataIndex: "address",
key: "address",
},
{
title: "Email",
dataIndex: "email",
key: "email",
},
{
title: "Phone",
dataIndex: "phone",
key: "pnone",
},
];

// create 10 dummy user
const dataSource = Array.from({ length: 10 }, (_, i) => ({
key: i,
name: `user-${i}`,
age: 32 + i,
address: `${i}-Downing Street`,
email: `user${i}@gmail.com`,
phone: 123456789,
}));


return (
<>
<Head>
<title>Create Next App</title>
<meta name="description" content="my awesome app" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<Wrapper>
<Typography.Title level={4} className={"title"}>
User list
</Typography.Title>
<Table
dataSource={dataSource}
columns={columns}
bordered
pagination={false}
/>
<div className={"btn__container"}>
<Button type="primary" >Print</Button>
</div>
</Wrapper>
</>
);
}

Now let’s write some CSS inside /page/app.style.ts

// /page/app.style.ts

import styled from "styled-components";
export const Wrapper = styled.main`
min-height: 100vh;
padding: 0 3rem;
.title {
margin-bottom: 10px;
}
.btn__container {
margin-top: 10px;
display: flex;
justify-content: flex-end;
}
`;

Now if you visit http://localhost:3000/ our app will look like this 👇

preview
Preview

Now let’s generate a PDF of this ☝️ and download it. To achieve we are going to use html2pdf.js library

pnpm i html2pdf.js

Now edit /page/index.tsx as follows

...
....
import html2pdf from 'html2pdf.js/dist/html2pdf.min.js';

export default function Home() {

.....
.....

// return UI
const content = () => {
return (
<>
<Typography.Title level={4} className={"title"}>
User list
</Typography.Title>
<Table
dataSource={dataSource}
columns={columns}
bordered
pagination={false}
/>
<div className={"btn__container"}>
<Button type="primary">Print</Button>
</div>
</>
);
};

//generate PDF
const handlePrint = () => {
const printElement = content();
console.log(printElement);
html2pdf().from(printElement).save();
};

return (
<>
<Head>
...
...
</Head>
<Wrapper>
{/* render UI */}
{content()}
<div className={"btn__container"}>
<Button type="primary" onClick={() => handlePrint()}>
Print
</Button>
</div>
</Wrapper>
</>
);

}

If we now run our application with the above code we will get the following error message.

This error occurs because the library (html2pdf) requires Web APIs to work, which are not available when Next.js pre-renders the page on the server side. The workaround for this is, we have to nextJs dynamic import.

...
....
import html2pdf from 'html2pdf.js/dist/html2pdf.min.js';

export default function Home() {

.....
.....

// return UI
const content = () => {
return (
<>
<Typography.Title level={4} className={"title"}>
User list
</Typography.Title>
<Table
dataSource={dataSource}
columns={columns}
bordered
pagination={false}
/>
<div className={"btn__container"}>
<Button type="primary">Print</Button>
</div>
</>
);
};

//generate PDF
const handlePrint = () => {
const html2pdf = (await import("html2pdf.js/dist/html2pdf.min.js")).default
const printContent = content();
console.log(printContent);
html2pdf().from(printContent).save();
};

return (
<>
<Head>
...
...
</Head>
<Wrapper>
{/* render UI */}
{content()}
<div className={"btn__container"}>
<Button type="primary" onClick={() => handlePrint()}>
Print
</Button>
</div>
</Wrapper>
</>
);

}

After clicking on print button here, you will notice that nothing is happening. Here is what the console log would show you in the dev tools of the browser.

log

So basically html2pdf.js package is expecting simple HTML and nothing is happening.

Let's convert JSX to HTML first using ReactDOMServer and then pass that to the html2pdf function.

/page/index.tsx

...
....
import ReactDOMServer from 'react-dom/server';

export default function Home() {

.....
.....

// return UI
const content = () => {
return (
<>
<Typography.Title level={4} className={"title"}>
User list
</Typography.Title>
<Table
dataSource={dataSource}
columns={columns}
bordered
pagination={false}
/>
<div className={"btn__container"}>
<Button type="primary">Print</Button>
</div>
</>
);
};

//generate PDF
const handlePrint = () => {
const html2pdf = (await import("html2pdf.js/dist/html2pdf.min.js")).default
const printElement = ReactDOMServer.renderToString(content())
html2pdf().set(opt).from(printContent).save()
};

return (
<>
<Head>
...
...
</Head>
<Wrapper>
{/* render UI */}
{content()}
<div className={"btn__container"}>
<Button type="primary" onClick={() => handlePrint()}>
Print
</Button>
</div>
</Wrapper>
</>
);

Code :
https://github.com/bj-budhathoki/pdf-with-nextjs

--

--