DEV Community

Cover image for Generating PDF files using Next.js
Wonder2210
Wonder2210

Posted on

Generating PDF files using Next.js

Next.js is a framework based on React, it´s quite popular lately, thanks to some awesome features, like SSR, SSG... some advantages over react vanilla. A common task to do in web development generates PDF´s maybe you need to show some extra info or generate reports of some dynamically generated data, there are many use cases for it, in one of my last projects, I´ve been figuring out how to do it directly from next.js, and today I´m gonna show you how you can do it too.

Set Up

First, we need to initialize our project as we would do usually using next.js but adding the library to generate pdf's jsPDF

mkdir next-pdf
cd ./next-pdf
yarn init --yes
yarn add react react-dom next js-pdf normalize.css
Enter fullscreen mode Exit fullscreen mode

PS:normalize.css is optional but useful, makes browsers render all elements more consistently, is highly suggested.

Now edit your package.json and add the next lines

...
"scripts": {
    "dev": "next",
    "start": "next start",
    "build": "next build",
  },
...
Enter fullscreen mode Exit fullscreen mode

and run

mkdir pages
Enter fullscreen mode Exit fullscreen mode

pages/index.js

const app =()=>(<h1>Hello world</h1>);

export default app;
Enter fullscreen mode Exit fullscreen mode

with this setup is enough to start, but if you like to use typescript everywhere (like me haha) you can use the next lines to use it.

touch tsconfig.json
yarn add --dev @types/react @types/node @types/jspdf typescript

Enter fullscreen mode Exit fullscreen mode

rename index.js to index.tsx and finally run

yarn run dev
Enter fullscreen mode Exit fullscreen mode

Ok Let's go for it

we´ll create a folder named src where gonna be placed our component to generate PDF´s and our styles, and our scaffolding will looks like this

/src/
   /components/
       GeneratePDF.tsx
   /styles/
       styles.css
/pages/
   index.tsx
   _app.tsx
Enter fullscreen mode Exit fullscreen mode

let's add global styles to our app, this is made on _app.tsx, importing styles.css and normalize.css:

import * as React from "react";
import "normalize.css"
import "../src/styles/styles.css";

const MyApp = ({ Component, pageProps }) => {
  return (
    <Component {...pageProps} />
  );
};

export default MyApp;
Enter fullscreen mode Exit fullscreen mode

our styles/styles.css

.content{
    display:flex;
    align-items: center;
    flex-direction: column;
}

.main  > .content > p, h1{
    font-family: sans-serif;

}

.main > .content > p{
    font-size: 1.7em;
    text-align: justify;
    width:80%;
}

.main  button{
    display: block;
    cursor: pointer;
    background-color: crimson;
    color: white;
    font-size: 1.5em;
    font-family: sans-serif;
    width:8em;
    height: 3em;
    font-weight: 500;
    border-radius: 5px;
    border-color: transparent;
    margin:0 auto 0 auto;
}

.main .button-container{
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-direction: row;
}
Enter fullscreen mode Exit fullscreen mode

now our main components
/src/components/GeneratePDF.tsx

import React from "react";
import { jsPDF,HTMLOptionImage } from "jspdf";
import { toPng,toCanvas } from "html-to-image";
type props = {

  html?: React.MutableRefObject<HTMLDivElement>;

};

const GeneratePdf: React.FC<props> = ({ html }) => {
  const generatePdf = () => {
      const doc = new jsPDF();

      let split=doc.splitTextToSize(document.getElementById("text").innerText,200);
      let image = document.getElementById("image").getAttribute('src');
      doc.text(document.querySelector(".content > h1").innerHTML,75,5);
      doc.addImage(image,70,7,60,60);
      doc.text(split,5,75);
      doc.output("dataurlnewwindow");  

  };

  const generateImage=async ()=>{
    const image = await toPng(html.current,{quality:0.95});
    const doc = new jsPDF();

      doc.addImage(image,'JPEG',5,22,200,160);
      doc.save();


  }
  return (

    <div className="button-container">
        <button onClick={generateImage}>
        Get PDF using image
      </button>
      <button onClick={generatePdf}>
        Get PDF as text
      </button>
    </div>

  );
};

export default GeneratePdf;
Enter fullscreen mode Exit fullscreen mode

explanation = we are creating 2 buttons to generate 2 pdf's with the same content but using 2 different approaches, generateImage will generate a image from our HTML , and we will put it inside a pdf , and generatePdf just create the pdf, taking the content from our Dom, all of them have their advantages and their disadvantages

Using Image:

Advantages

✅ the result is exactly like on your page
✅ easy to set up

Disadvantages

❌ Slow to generate
❌ the pdf file weight is relatively high
❌ you can't copy and paste the content(if it matters for you)

Using Content from the dom:

Advantages

✅ Lightweight file size
✅ Fast Generation
✅ The text is selectable

Disadvantages

❌ is not so easy to set up everything in their own place

let's continue with pages/index.tsx

import * as React from "react";
import Image from "next/image";
import dynamic from "next/dynamic";
const GeneratePDF = dynamic(()=>import("./../src/components/GeneratePDF"),{ssr:false});
const app =()=>{
        const ref = React.useRef();

        return(<div className="main">
        <div className="content" ref={ref}>
        <h1>Hello PDF</h1>
        <img id="image" src="/images/image_header.jpg" width="300" height="200"/>
        <p id="text">
            Lorem ipsum dolor sit, amet consectetur adipisicing elit. Quisquam animi, molestiae quaerat assumenda neque culpa ab aliquam facilis eos nesciunt! Voluptatibus eligendi vero amet dolorem omnis provident beatae nihil earum!
            Lorem, ipsum dolor sit amet consectetur adipisicing elit. Ea, est. Magni animi fugit voluptates mollitia officia libero in. Voluptatibus nisi assumenda accusamus deserunt sunt quidem in, ab perspiciatis ad rem.
            Lorem ipsum dolor sit amet consectetur adipisicing elit. Nihil accusantium reprehenderit, quasi dolorum deserunt, nisi dolores quae officiis odio vel natus! Pariatur enim culpa velit consequatur sapiente natus dicta alias!
            Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur, asperiores error laudantium corporis sunt earum incidunt expedita quo quidem delectus fugiat facilis quia impedit sit magni quibusdam ipsam reiciendis quaerat!
        </p>
        </div>
        <GeneratePDF html={ref}/>
        </div>);
}

export default app;
Enter fullscreen mode Exit fullscreen mode

Explanation

As we can see is another component made with react ... well not at all, as you can see we are using dynamic from next

import dynamic from "next/dynamic";
const GeneratePDF = dynamic(()=>import("./../src/components/GeneratePDF"),{ssr:false});
Enter fullscreen mode Exit fullscreen mode

with this we are importing the component dynamically (basically only when is required) and we are deactivating the SSR (Server side rendering) because jsPDF requires to be initialized on the browser, otherwise, we would catch an error from it.

now you can generate PDF's from your next app, there are many approaches you can use for example auto generate tables

Thanks for reading

If you have any question, or suggestion please, leave a comment below, and please follow me here and also on my twitter take care 👍

Top comments (4)

Collapse
 
dramoslace profile image
Deiby Dayans R

Could we use some SVG's within the HTML? Also please add some comments within the code, it would be better to understand each of the methods you are using

Collapse
 
wonder2210 profile image
Wonder2210

Thanks for the feedback , That's a good question , i'll find out about it , cheers !!

Collapse
 
vikbert profile image
Xun Zhou

why not open-source your example?

Collapse
 
chenwi32 profile image
Chenwi Eugene

Thank you very much Sir, You've helped me today a lot.