@react-pdf/renderer vs react-pdf
React 環境における PDF 生成と表示のアーキテクチャ選定
@react-pdf/rendererreact-pdf類似パッケージ:

React 環境における PDF 生成と表示のアーキテクチャ選定

@react-pdf/rendererreact-pdf は、どちらも React エコシステムで PDF を扱うためのライブラリですが、その目的と動作原理は完全に異なります。

@react-pdf/renderer は、React コンポーネントの構文を使って PDF ドキュメントを生成するためのライブラリです。独自のレンダラーを持ち、React の要素ツリーを解析してバイナリ形式の PDF ファイルを構築します。サーバーサイド(Node.js)でもクライアントサイド(ブラウザ)でも動作し、動的なレポートや請求書作成に適しています。

一方、react-pdf は、既存の PDF ファイルを Web ブラウザー上で表示するためのライブラリです。Mozilla の pdf.js エンジンをラップしており、PDF ファイルを読み込んで Canvas 要素として描画します。ドキュメントの閲覧、ページネーション、ズーム機能を提供しますが、PDF を新規作成する機能はありません。

npmのダウンロードトレンド

3 年

GitHub Starsランキング

統計詳細

パッケージ
ダウンロード数
Stars
サイズ
Issues
公開日時
ライセンス
@react-pdf/renderer016,724292 kB4274ヶ月前MIT
react-pdf011,137309 kB195ヶ月前MIT

@react-pdf/renderer vs react-pdf: PDF 生成と表示の明確な役割分担

React プロジェクトで PDF 処理が必要になった際、@react-pdf/rendererreact-pdf という名前が似ている 2 つのパッケージが存在することに混乱する開発者は少なくありません。しかし、これらは解決する問題が真逆です。一方は「PDF を作る」ためのツールであり、もう一方は「PDF を見る」ためのツールです。この違いを理解せずに選定を誤ると、アーキテクチャ全体が行き詰まることになります。

本記事では、両者の技術的な仕組み、実装方法、そして具体的なユースケースに基づき、どちらを採用すべきかを明確にします。

🏗️ 根本的な違い:生成 (Generation) vs 表示 (Viewing)

まず最も重要な点は、両者が扱うデータフローの方向性が反対だということです。

@react-pdf/renderer は、React コンポーネントツリーを入力として受け取り、PDF バイナリデータを出力します。内部に独自のレンダリングエンジンを持っており、HTML や CSS を解釈するのではなく、@react-pdf 固有のコンポーネント(<View>, <Text>, <Page> など)を PDF の構造命令に変換します。

// @react-pdf/renderer: PDF を「生成」するコード
import { Document, Page, Text, View, StyleSheet } from '@react-pdf/renderer';

const MyDocument = () => (
  <Document>
    <Page size="A4" style={styles.page}>
      <View style={styles.section}>
        <Text>請求書番号:12345</Text>
        <Text>金額:¥10,000</Text>
      </View>
    </Page>
  </Document>
);

// これを <PDFDownloadLink> や pdf() 関数でバイナリとして出力します

react-pdf は、既存の PDF ファイル(URL や Blob)を入力として受け取り、ブラウザ上の <canvas> 要素に描画します。内部では Mozilla 製の pdf.js を使用しており、PDF の仕様を解析して視覚化します。React コンポーネントとして振る舞いますが、中身はあくまで「ビューワー」です。

// react-pdf: PDF を「表示」するコード
import { Document, Page, pdfjs } from 'react-pdf';
import 'react-pdf/dist/esm/Page/AnnotationLayer.css';
import 'react-pdf/dist/esm/Page/TextLayer.css';

pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.min.js`;

function MyApp() {
  return (
    <div>
      <Document file="/path/to/invoice.pdf">
        <Page pageNumber={1} />
      </Document>
    </div>
  );
}

🛠️ 実装アプローチと API 設計

両者は似たようなコンポーネント名(Document, Page)を使っていますが、その実体とプロパティは全く異なります。ここを混同するとビルドエラーや予期せぬ動作の原因になります。

レイアウトとスタイリング

@react-pdf/renderer は、Flexbox に基づいた独自のスタイリングシステムを採用しています。CSS のサブセットのような記述が可能ですが、Web ブラウザの CSS 実装とは異なる制約(例えば、div ではなく View を使う、特定の単位しか使えないなど)があります。これは PDF 仕様という固定された出力形式に合わせているためです。

// @react-pdf/renderer: 独自コンポーネントと Flexbox スタイル
import { StyleSheet } from '@react-pdf/renderer';

const styles = StyleSheet.create({
  page: { padding: 30, flexDirection: 'row' },
  column: { width: '50%', padding: 10 },
  text: { fontSize: 12, fontFamily: 'Helvetica' }
});

const Invoice = ({ data }) => (
  <View style={styles.page}>
    <View style={styles.column}>
      <Text style={styles.text}>{data.sender}</Text>
    </View>
    <View style={styles.column}>
      <Text style={styles.text}>{data.receiver}</Text>
    </View>
  </View>
);

react-pdf は、PDF ファイルそのものが既にレイアウト情報を持っているため、スタイリングの概念はありません。開発者が制御できるのは、表示されるページのサイズ、スケール、回転、そしてキャンバスの配置だけです。CSS で制御するのは、PDF が描画されるコンテナ要素のみです。

// react-pdf: 表示制御のみ。コンテンツのスタイル変更は不可
function Viewer({ fileUrl }) {
  const [numPages, setNumPages] = useState(null);

  function onDocumentLoadSuccess({ numPages }) {
    setNumPages(numPages);
  }

  return (
    <div style={{ display: 'flex', justifyContent: 'center' }}>
      <Document 
        file={fileUrl} 
        onLoadSuccess={onDocumentLoadSuccess}
        // コンテンツ自体のスタイル変更はここでできない
      >
        {Array.from(new Array(numPages), (el, index) => (
          <Page 
            key={`page_${index + 1}`} 
            pageNumber={index + 1} 
            scale={1.5} 
            renderTextLayer={true}
          />
        ))}
      </Document>
    </div>
  );
}

データの動的処理

@react-pdf/renderer の真価は、React の状態管理やプロパティを活用して、動的にコンテンツを変化させられる点にあります。JavaScript のロジックをそのまま使って、条件分岐やループを行いながら PDF を組み立てられます。

// @react-pdf/renderer: JavaScript ロジックで動的に内容を構築
const OrderItems = ({ items }) => (
  <View>
    {items.map((item, i) => (
      <View key={i} style={{ flexDirection: 'row', borderBottom: '1px solid #eee' }}>
        <Text style={{ width: '60%' }}>{item.name}</Text>
        <Text style={{ width: '40%', textAlign: 'right' }}>
          {item.price.toLocaleString()} 円
        </Text>
      </View>
    ))}
  </View>
);

react-pdf において、PDF 内部のテキストや画像を React の状態で書き換えることはできません。できるのは「どのページを表示するか」「どのくらい拡大するか」といったビューワーとしての操作のみです。もし内容を書き換えたい場合は、バックエンドなどで別途 PDF を生成し直す必要があります。

// react-pdf: 操作できるのはページ番号や拡大率のみ
function ControlledViewer({ file }) {
  const [page, setPage] = useState(1);

  return (
    <div>
      <button onClick={() => setPage(prev => prev - 1)} disabled={page <= 1}>前へ</button>
      <span>ページ {page}</span>
      <button onClick={() => setPage(prev => prev + 1)}>次へ</button>
      
      <Document file={file}>
        <Page pageNumber={page} />
      </Document>
    </div>
  );
}

🌐 実行環境とサーバーサイドレンダリング (SSR)

実行環境のサポート状況も大きな分岐点です。

@react-pdf/renderer は、Node.js 環境での実行を強く意識して設計されています。これにより、Next.js や Remix などのフレームワークを用いたサーバーサイドレンダリング(SSR)や、API ルート内での PDF 生成が容易です。ブラウザ依存の API を極力排除しており、サーバー上で pdf() 関数を呼び出してバイナリストリームを返すような処理が一般的です。

// Node.js (API Route) での使用例
import { pdf } from '@react-pdf/renderer';
import MyDocument from './MyDocument';

export async function GET() {
  const blob = await pdf(<MyDocument data={...} />).toBlob();
  // blob をレスポンスとして返す
}

react-pdf は、描画に HTML5 Canvas を使用するため、原則としてクライアントサイド(ブラウザ)での実行が必須です。サーバーサイドで PDF を描画することはできません(Node.js で Canvas をエミュレートする試みは存在しますが、公式の主要ユースケースではありません)。SSR を行う場合は、ハイドレーションミスマッチを防ぐために「クライアントmounted 後に表示する」ような制御が必要です。

// react-pdf: クライアントサイド専用としての保護
import { useEffect, useState } from 'react';

function SafeViewer({ file }) {
  const [isClient, setIsClient] = useState(false);

  useEffect(() => {
    setIsClient(true);
  }, []);

  if (!isClient) return <div>読み込み中...</div>;

  return <Document file={file}><Page pageNumber={1} /></Document>;
}

📊 選定ガイド:どちらを使うべきか

実務での判断基準を整理します。

@react-pdf/renderer を選ぶべきケース

  • 動的ドキュメントの生成: ユーザーごとの請求書、納品書、チケット、証明書などを生成したい。
  • サーバーサイド生成: API エンドポイントで PDF ファイルを生成してダウンロードさせたい。
  • プリント機能: Web サイトの内容をそのまま印刷するのではなく、印刷用に最適化されたレイアウトの PDF を作りたい。
  • React の知識を活かしたい: HTML/CSS の知識ではなく、React コンポーネントの構成力で PDF を設計したい。

react-pdf を選ぶべきケース

  • ドキュメント閲覧機能: アップロードされた PDF や、既存のマニュアルをアプリ内で表示したい。
  • プレビュー機能: ユーザーがアップロードしたファイルが正しいか確認するプレビュー画面を作りたい。
  • PDF リーダーアプリ: ページ送り、ズーム、検索機能付きの PDF 閲覧アプリを構築したい。
  • 生成不要: PDF を作る必要はなく、あくまで「見る」ことだけが目的である。

⚠️ 注意点と落とし穴

  1. 名前の混同: npm インストール時に npm install react-pdfnpm install @react-pdf/renderer を間違えないように注意してください。パッケージ名が似ているため、誤ってインストールしてしまう事故が頻発しています。
  2. フォント扱い: @react-pdf/renderer では、日本語フォントなどを扱う際に、フォントファイルの登録プロセスが別途必要です。標準のフォントだけでは日本語が表示できない場合があるため、ドキュメントをよく確認してください。
  3. パフォーマンス: react-pdf で巨大な PDF を表示する場合、すべてのページを一度にレンダリングするとブラウザが重くなります。必要に応じて表示範囲を制限する実装が求められます。

💡 結論

この 2 つは競合関係ではなく、補完関係にあります。

  • PDF を「作る」必要があるなら → 迷わず @react-pdf/renderer です。React の宣言的な記法で柔軟なドキュメントを生成できます。
  • PDF を「見る」必要があるならreact-pdf が唯一の選択肢です。高機能なビューワーを簡単に実装できます。

場合によっては、@react-pdf/renderer で動的に PDF を生成し、生成されたファイルを react-pdf で即時プレビュー表示する、というように両者を組み合わせるアーキテクチャも有効です。それぞれの役割を正しく理解し、プロジェクトの要件に合わせて適切に使い分けてください。

選び方: @react-pdf/renderer vs react-pdf

  • @react-pdf/renderer:

    既存の PDF ファイルをアプリ内で表示させたい場合、または PDF の閲覧機能(ズーム、ページ送り)が主な要件である場合は react-pdf を選択してください。これは PDF リーダーやドキュメント管理システムのビューワー部分を実装する際の標準的な選択肢です。ただし、このパッケージで PDF を新規作成することはできません。

  • react-pdf:

    React コンポーネントとして PDF ドキュメントをゼロから設計し、動的なデータに基づいて PDF ファイルを生成したい場合は @react-pdf/renderer を選択してください。請求書、レポート、証明書など、コンテンツがユーザーごとに変化するドキュメントをサーバー側またはクライアント側で生成する際に最適です。既存の PDF ファイルを表示する用途には向きません。

@react-pdf/renderer のREADME

React renderer for creating PDF files on the browser and server

How to install

yarn add @react-pdf/renderer

How it works

import React from 'react';
import { Document, Page, Text, View, StyleSheet } from '@react-pdf/renderer';

// Create styles
const styles = StyleSheet.create({
  page: {
    flexDirection: 'row',
    backgroundColor: '#E4E4E4',
  },
  section: {
    margin: 10,
    padding: 10,
    flexGrow: 1,
  },
});

// Create Document Component
const MyDocument = () => (
  <Document>
    <Page size="A4" style={styles.page}>
      <View style={styles.section}>
        <Text>Section #1</Text>
      </View>
      <View style={styles.section}>
        <Text>Section #2</Text>
      </View>
    </Page>
  </Document>
);

Web. Render in DOM

import React from 'react';
import ReactDOM from 'react-dom';
import { PDFViewer } from '@react-pdf/renderer';

const App = () => (
  <PDFViewer>
    <MyDocument />
  </PDFViewer>
);

ReactDOM.render(<App />, document.getElementById('root'));

Node. Save in a file

import React from 'react';
import ReactPDF from '@react-pdf/renderer';

ReactPDF.render(<MyDocument />, `${__dirname}/example.pdf`);

Contributors

This project exists thanks to all the people who contribute. Looking to contribute? Please check our [contribute] document for more details about how to setup a development environment and submitting code.

Sponsors

Thank you to all our sponsors! [Become a sponsors]

Backers

Thank you to all our backers! [Become a backer]

License

MIT © Diego Muracciole