Generate PDF using nextJS , html2pdf.js , Antd Design, and styled component
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 👇
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.
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>
</>
);