📈

Nextjsでpdfダウンロード機能実装

2024/10/10に公開2

コードをPDFでダウンロードするために、いくつかのライブラリを調査してみました。


html2pdf、jspdf、pdf-lib、pdfmake、puppeteer、react-pdfといった複数の選択肢がありましたが、その中で最も利用されているのがpuppeteerでした。

また、最近まで更新されている履歴があり、メンテナンスが行き届いていることも確認できました。

html2pdf

https://www.npmjs.com/package/html2pdf.js/v/0.9.0
メリット:
・HTML要素を簡単にPDFに変換できます。
・CSSスタイルをそのまま適用できるため、デザインの実現が容易です。
・使い方が簡単で、素早くPDFを生成することができます。
デメリット: ・大きなHTMLドキュメントや複雑なレイアウトを処理する際に、パフォーマンスの問題が発生することがあります。 ・PDFの細かなコントロールは制限される場合があります。

jsPDF

https://raw.githack.com/MrRio/jsPDF/master/docs/index.html
メリット: ・PDFの詳細なコントロールが可能で、ページサイズ、フォント、画像などを自由に設定できます。 ・動的なPDF生成が必要な場合に有利です。 ・テキスト、画像、図形などをコードで直接作成できます。
デメリット: ・HTMLをそのままPDFに変換する作業は手間がかかることがあります。 ・CSSスタイルをそのまま反映させるのが難しいです。

pdf-lib

https://www.npmjs.com/package/pdf-lib/v/1.3.1
メリット: ・既存のPDFファイルを修正したり、新しいページを追加する作業が可能です。 ・TypeScriptのサポートが充実しており、Next.jsプロジェクトとの互換性が高いです。 ・PDFのテキスト、画像、フォームフィールドなどを操作することができます。
デメリット: ・単純にHTMLをPDFに変換する機能は提供されていません。 ・PDFの要素をコードで直接作成する必要があるため、時間がかかる場合があります。

Puppeteer

https://pptr.dev/category/introduction
メリット: ・サーバー側でブラウザを通じてHTMLをPDFにレンダリングするため、CSSを完璧に反映できます。 ・複雑なレイアウトのHTMLをPDFに変換する際に有利です。
デメリット: ・サーバーでのみ使用でき、クライアント側では使用できません。 ・設定が複雑になる場合があり、サーバーリソースを多く消費する可能性があります。

React-pdf

https://react-pdf.org/
メリット: ・Reactと自然に統合でき、既存のフォームデータをPDFに簡単に変換できます。 ・多様なスタイリングオプションをサポートし、条件付きレンダリングも可能です。
デメリット: ・HTMLとの直接的な変換機能はありません。JSX形式でPDFレイアウトを作成する必要があります。 ・複雑なレイアウトを実装するには時間がかかる場合があります。

pdfmake

http://pdfmake.org/#/
メリット: ・非常にカスタマイズ性が高く、多様なスタイリングオプションを提供します。 ・ブラウザとNode.js環境の両方で使用可能です。
デメリット: ・JSONベースのフォーマットを使用するため、複雑なレイアウトを実装する際には少し手間がかかることがあります。
クライアントのみで作業を進める場合はReact-pdfまたはpdfmakeが適しており、サーバーとクライアントの両方を活用できる場合はPuppeteerが良いと考えます。

Puppeteerをサーバーで使用してPDFダウンロード機能を実装すると、サーバーインフラのコストが増加する可能性があるため(SSRで作成したときにAWSの料金が非常に高くなった経験があります...)

まずはjsPDFで実装してみました。
jsPDFライブラリをインストールしました。

npm install jspdf

フォームデータを入力できるシンプルなReactコンポーネントを以下に例示しました。

// components/FormComponent.tsx
"use client";

import { useState } from "react";

interface FormData {
  name: string;
  position: string;
}

interface FormComponentProps {
  onSubmit: (data: FormData) => void;
}

const FormComponent: React.FC<FormComponentProps> = ({ onSubmit }) => {
  const [name, setName] = useState("");
  const [position, setPosition] = useState("");

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    onSubmit({ name, position });
  };

  return (
    <form
      onSubmit={handleSubmit}
      className="space-y-4 p-6 bg-white rounded-lg shadow-md"
    >
      <div>
        <label className="block text-sm font-medium text-gray-700">
          お名前:
        </label>
        <input
          type="text"
          value={name}
          onChange={(e) => setName(e.target.value)}
          className="p-2 mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
          placeholder="お名前を入力してください。"
        />
      </div>
      <div>
        <label className="block text-sm font-medium text-gray-700">職種:</label>
        <input
          type="text"
          value={position}
          onChange={(e) => setPosition(e.target.value)}
          className="p-2 mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
          placeholder="職種を入力してください。"
        />
      </div>
      <button
        type="submit"
        className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
      >
        Generate PDF
      </button>
    </form>
  );
};

export default FormComponent;

jsPDFを使用してPDFファイルを生成し、ダウンロードするコンポーネントを作成します。

// components/DownloadPDF.tsx
"use client";

import jsPDF from "jspdf";

interface DownloadPDFProps {
  formData: {
    name: string;
    position: string;
  };
}

const DownloadPDF: React.FC<DownloadPDFProps> = ({ formData }) => {
  const handleDownload = () => {
    const doc = new jsPDF();

    // PDF コンテンツ設定
    doc.text(`お名前: ${formData.name}`, 10, 10);
    doc.text(`職種: ${formData.position}`, 10, 20);

    // PDF ファイルDL
    doc.save("resume.pdf");
  };

  return (
    <button
      onClick={handleDownload}
      className="mt-4 w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500"
    >
      Download PDF
    </button>
  );
};

export default DownloadPDF;

Next.jsのApp Routerを使用してページを構成し、入力フォームとPDFダウンロードボタンをレンダリングします。

// app/pdf/page.tsx
"use client";

import { useState } from "react";
import FormComponent from "../components/FormComponent";
import DownloadPDF from "../components/DownloadPDF";

const PdfPage: React.FC = () => {
  const [formData, setFormData] = useState<{
    name: string;
    position: string;
  } | null>(null);

  const handleFormSubmit = (data: { name: string; position: string }) => {
    setFormData(data);
  };

  return (
    <div className="min-h-screen flex items-center justify-center bg-gray-100">
      <div className="w-full max-w-md space-y-8">
        <h1 className="text-2xl font-bold text-center text-gray-900">
          PDF ダウンロード
        </h1>
        <FormComponent onSubmit={handleFormSubmit} />
        {formData && <DownloadPDF formData={formData} />}
      </div>
    </div>
  );
};

export default PdfPage;

フォームに入力して生成ボタンを押すと、PDFダウンロードボタンが表示されます!

ダウンロードしてPDFを開いてみたら、日本語が文字化けしていました。
jsPDFでは基本的に英語以外はサポートされていないとのことです。
そのため、jsPDFで使用できるフォントを追加する必要があります。

日本語フォントをダウンロードし、
ttfファイルをBase64に変換してjsPDFで使用できるようにします。
Base64エンコードをチェックすると、Base64コードを取得できます。

https://transfonter.org/
参考サイト:
https://zenn.dev/knaka0209/books/f21fd9c9bb6130/viewer/6ba386

Discussion

TT

https://npm-compare.com/ja-JP/@react-pdf/renderer,jspdf,pdf-lib,pdfkit,pdfmake,react-pdf によると、

  • 基本的なクライアントサイドのPDF生成には:jsPDFを選んでください。
  • ReactアプリでPDFをレンダリングするには:react-pdfを使用してください。
  • サーバーサイドのPDF生成には:pdfmakeまたはpdfkitを検討してください。
  • 既存のPDFを操作するには:pdf-libを使用してください。
  • Reactベースの宣言的PDF作成には:@react-pdf/rendererを使用してください。
ソニソニ

ありがとうございます!
各用途に合わせたPDFライブラリの選び方がとても分かりやすいですね!✨
プロジェクトに応じて活用させていただきます。