2026/04/12

PDF生成API vs ブラウザ印刷(window.print)|違いと使い分け完全ガイド

PDF生成window.printブラウザ印刷CSS

「とりあえず window.print() で」――Webアプリに印刷機能を追加するとき、多くの開発者が最初に選ぶアプローチです。シンプルで、追加ライブラリ不要で、数行で動きます。

しかし要件が増えるにつれ、壁にぶつかります。ブラウザごとに出力が違う。改ページが思い通りにならない。サーバーサイドでバッチ生成したい。そのとき初めて「PDF生成API」という選択肢を検討することになります。

この記事では、window.print() とPDF生成APIの違いを具体的なコード例とともに比較し、どちらを使うべきかの判断基準を明確にします。

window.print()の仕組み

window.print() はブラウザのネイティブAPIで、現在表示中のページを印刷ダイアログ経由で出力します。

function printInvoice() {
  window.print();
}

通常は @media print を使って印刷時のスタイルを制御します。

@media print {
  /* ナビゲーションやボタンを非表示 */
  .no-print, nav, .sidebar {
    display: none;
  }

  body {
    font-size: 12pt;
    color: #000;
  }

  /* リンクURLを括弧で表示 */
  a[href]::after {
    content: " (" attr(href) ")";
  }
}

この方法は、ブログ記事やシンプルなページの印刷には十分です。問題は、業務で使えるPDFを生成しようとしたときに始まります。

ブラウザ印刷の7つの限界

1. サーバーサイドで生成できない

window.print() はクライアント側のAPIです。ユーザーがブラウザで操作しない限り、PDFは生成されません。毎月1,000件の請求書を自動生成するような要件には対応できません。

2. ブラウザごとに出力が異なる

Chrome、Safari、Firefoxはそれぞれ独自のレンダリングエンジンを使います。同じHTMLでも、余白・フォントサイズ・改ページ位置が微妙に(時には大きく)異なります。「Chromeで完璧に見えていたのにSafariで崩れた」というのはよくある話です。

3. 改ページの制御が不完全

CSS の page-break-before / break-before は仕様上存在しますが、実際のブラウザ実装は不完全です。特に複雑なレイアウト(ネストされたFlexboxやGrid内の要素)では、意図した位置で改ページされない問題が頻発します。詳しくはCSS改ページの完全ガイドを参照してください。

4. ヘッダー・フッターをページごとに追加できない

ブラウザの印刷機能では、各ページにカスタムヘッダー・フッター(会社ロゴ、ページ番号、「Confidential」表記など)を挿入できません。@page ルールではマージン設定のみが可能で、任意のHTML要素は配置できません。

5. パスワード保護やウォーターマークが付けられない

機密性の高い文書では必須のPDFパスワード保護や、ドラフト文書への「DRAFT」ウォーターマーク挿入は、ブラウザの印刷機能では不可能です。

6. バッチ処理ができない

100件の見積書を一括でPDF化したい場合、window.print() では各ユーザーが1件ずつブラウザで操作する必要があります。バックグラウンドでの一括生成は対応できません。

7. ファイルサイズと品質がブラウザ依存

同じページをPDF出力しても、Chromeは2MB、Safariは5MBといった差が出ることがあります。画像の圧縮方式やフォントの埋め込み方法がブラウザごとに異なるためです。

PDF生成APIとの比較表

機能 window.print() PDF生成API
サーバーサイド生成 不可 可能
出力の一貫性 ブラウザ依存 常に同一
改ページ制御 限定的 完全制御
ヘッダー・フッター 不可 ページ毎に設定可
バッチ生成 不可 可能
パスワード保護 不可 可能
ウォーターマーク 不可 可能
コスト 無料 APIプラン
セットアップ 不要 API連携が必要
ユーザー操作 必要 不要

移行すべきタイミング

以下のフローチャートで、あなたのプロジェクトにどちらが適しているか判断できます。

ユーザー操作なしでPDFを生成したい?
├── Yes → PDF生成API
└── No
    ├── ブラウザ間で同一の出力が必要?
    │   ├── Yes → PDF生成API
    │   └── No
    │       ├── ページごとのヘッダー/フッターが必要?
    │       │   ├── Yes → PDF生成API
    │       │   └── No → window.print() で十分
    │       └──
    └──

window.print()で十分なケース:

  • 社内ツールで「このページを印刷」ボタンがあればいい
  • 出力品質にブラウザ差があっても問題ない
  • PDFをサーバーに保存する必要がない

PDF生成APIに切り替えるべきケース:

  • 請求書・見積書をシステムから自動送付している
  • 顧客向けにブランド統一されたPDFを提供したい
  • 月次レポートのバッチ生成が必要
  • Puppeteerの運用コストを下げたい

window.print()からAPIへの移行例

実際のコードで、移行がどれだけシンプルかを見てみましょう。

Before: クライアントサイド(window.print)

// ユーザーがボタンをクリック → ブラウザの印刷ダイアログが開く
document.getElementById('print-btn').addEventListener('click', () => {
  window.print();
});

After: PDF生成API呼び出し

document.getElementById('download-btn').addEventListener('click', async () => {
  const response = await fetch('https://pdf.funbrew.cloud/api/pdf/generate', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer sk-your-api-key',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      html: document.getElementById('invoice').innerHTML,
      options: { 'page-size': 'A4' }
    })
  });

  const blob = await response.blob();
  const url = URL.createObjectURL(blob);
  window.open(url);
});

違いは明確です。window.print() はブラウザの印刷ダイアログを開くだけですが、APIは制御可能なPDFファイルを返します。サーバーサイドで同じAPIを呼べば、ユーザー操作なしでの一括生成も可能です。

各言語での実装例はクイックスタートガイドで確認できます。

シナリオ別:どちらを選ぶべきか

抽象的な比較だけでは判断しにくいので、よくある業務シナリオごとに具体的に見ていきましょう。

シナリオ1: 請求書の自動発行

ECサイトで注文完了時にPDF請求書を自動生成し、メール添付で送信するケース。

window.print()の場合: 不可能。ユーザーのブラウザ操作が必須なため、バックエンドからの自動送信には使えません。管理画面で1件ずつ手動印刷するしかありません。

PDF生成APIの場合:

// 注文確定時にサーバーサイドで自動実行
async function generateInvoice(order) {
  const html = renderInvoiceTemplate(order);

  const response = await fetch('https://pdf.funbrew.cloud/api/pdf/generate', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.PDF_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      html,
      options: { 'page-size': 'A4' }
    })
  });

  const pdfBuffer = await response.arrayBuffer();
  await sendEmailWithAttachment(order.email, pdfBuffer);
}

シナリオ2: 月次レポートのバッチ生成

毎月1日に全顧客向けの利用レポートをPDFで生成するケース。

window.print()の場合: 不可能。100社分のレポートを人が1つずつブラウザで開いて印刷する必要があります。

PDF生成APIの場合:

// cronジョブで毎月1日に実行
async function generateMonthlyReports() {
  const customers = await getActiveCustomers();

  for (const customer of customers) {
    const data = await getUsageData(customer.id);
    const html = renderReportTemplate(customer, data);

    const pdf = await generatePdf(html, {
      'page-size': 'A4',
      'header-html': renderHeader(customer.name),
      'footer-html': renderFooter()
    });

    await uploadToStorage(pdf, `reports/${customer.id}/${getMonth()}.pdf`);
    await notifyCustomer(customer, pdf);
  }
}

シナリオ3: 修了証・証明書の発行

オンライン研修完了時に、受講者名入りの修了証PDFを即座に発行するケース。

window.print()の場合: 可能だが問題あり。ブラウザごとにフォントやレイアウトが変わるため、正式な証明書としての品質が保証できません。受講者が「印刷」ボタンを押し忘れるとPDFが残りません。

PDF生成APIの場合: サーバー側で一貫した品質のPDFを生成し、ダウンロードリンクを提供。生成と同時にDBに記録を保存できます。証明書自動生成の詳細も参照してください。

シナリオ4: 社内ヘルプページの印刷

社内Wikiのページを「そのまま印刷」するだけのケース。

window.print()の場合: これで十分です。出力品質のブラウザ差は許容でき、サーバー保存も不要。@media print で不要な要素を非表示にするだけで対応できます。

判断のポイント: PDFの品質・一貫性が重要でなく、人が手動で操作する前提なら、window.print() の方がシンプルで適切です。

サーバーサイドでの移行例(複数言語)

window.print() からPDF生成APIへの移行は、フロントエンドだけでなくサーバーサイドからも呼び出せるのが大きなメリットです。

Node.js(Express)

const express = require('express');
const app = express();

app.post('/api/invoices/:id/pdf', async (req, res) => {
  const invoice = await Invoice.findById(req.params.id);
  const html = renderTemplate('invoice', invoice);

  const pdfResponse = await fetch('https://pdf.funbrew.cloud/api/pdf/generate', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.PDF_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ html, options: { 'page-size': 'A4' } })
  });

  const pdf = await pdfResponse.arrayBuffer();
  res.setHeader('Content-Type', 'application/pdf');
  res.setHeader('Content-Disposition', `attachment; filename="invoice-${invoice.number}.pdf"`);
  res.send(Buffer.from(pdf));
});

Python(Flask / Django)

import requests

def generate_invoice_pdf(invoice):
    html = render_template('invoice.html', invoice=invoice)

    response = requests.post(
        'https://pdf.funbrew.cloud/api/pdf/generate',
        headers={
            'Authorization': f'Bearer {os.environ["PDF_API_KEY"]}',
            'Content-Type': 'application/json',
        },
        json={
            'html': html,
            'options': {'page-size': 'A4'}
        }
    )

    return response.content  # PDF bytes

PHP(Laravel)

use Illuminate\Support\Facades\Http;

public function downloadInvoicePdf(Invoice $invoice)
{
    $html = view('pdf.invoice', compact('invoice'))->render();

    $response = Http::withToken(config('services.pdf.key'))
        ->post('https://pdf.funbrew.cloud/api/pdf/generate', [
            'html' => $html,
            'options' => ['page-size' => 'A4'],
        ]);

    return response($response->body())
        ->header('Content-Type', 'application/pdf')
        ->header('Content-Disposition', "attachment; filename=\"invoice-{$invoice->number}.pdf\"");
}

各言語のさらに詳しい実装例は言語別クイックスタートガイドで確認できます。TypeScript、Go、Rubyなどの例も掲載しています。

@media print CSSの落とし穴10選

window.print() を使い続ける場合でも、以下のCSS問題を知っておくと無駄なデバッグ時間を減らせます。

1. FlexboxとGridがprint時に崩れる

多くのブラウザの印刷エンジンは、FlexboxやCSS Gridのレンダリングが画面表示と異なります。印刷用にレイアウトをフォールバックする必要があります。

@media print {
  .flex-container {
    display: block; /* Flexboxを無効化 */
  }

  .grid-layout {
    display: block; /* Gridを無効化 */
  }

  .grid-layout > * {
    width: 100%;
    margin-bottom: 1rem;
  }
}

2. 背景色がデフォルトで印刷されない

ブラウザはインク節約のため、デフォルトで背景色を印刷しません。-webkit-print-color-adjust を使って強制できますが、全ブラウザで動作するわけではありません。

@media print {
  .highlight-box {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
    background-color: #fff3cd !important;
  }
}

3. position: fixedが印刷時に各ページに繰り返される

画面表示で固定ヘッダーやフッターに使う position: fixed は、印刷時に全ページで繰り返し表示されてしまうことがあります(特にChrome)。

@media print {
  .fixed-header, .sticky-nav {
    position: static; /* fixedを解除 */
  }
}

4. box-shadowが印刷で巨大なインク消費になる

画面では効果的なbox-shadowですが、印刷すると不要なインク消費とレンダリング負荷になります。

@media print {
  * {
    box-shadow: none !important;
  }
}

5. vhやvw単位がページサイズと一致しない

100vh はブラウザのビューポート高さですが、印刷時のページ高さとは一致しません。A4の場合、実際のページ高さは約297mmです。

@media print {
  .full-page-section {
    height: auto; /* vhを解除 */
    page-break-inside: avoid;
  }
}

6. iframeの中身が印刷されない

window.print() は現在のドキュメントのみを対象とし、iframe内のコンテンツは印刷されません(または空白になります)。動的に埋め込まれたチャートやウィジェットが消えることがあります。

7. CSSアニメーションが印刷結果に影響する

アニメーション途中の状態で印刷されることがあります。opacity: 0 からフェードインするアニメーションの場合、印刷時に要素が消えることも。

@media print {
  * {
    animation: none !important;
    transition: none !important;
    opacity: 1 !important;
  }
}

8. Web Fontsが印刷時に適用されない場合がある

Google Fontsなどの外部フォントは、印刷時にフォールバックフォントに置き換わることがあります。特にオフライン環境やネットワークが遅い場合に発生します。

@media print {
  body {
    font-family: "Noto Sans JP", "Hiragino Sans", sans-serif;
  }
}

9. display: noneの要素がプレビューと実印刷で異なる

@media printdisplay: none にした要素が、印刷プレビューでは消えているのに実際の印刷では表示される、あるいはその逆のケースがあります。ブラウザのバグに起因するため、テストは実際のPDF出力で確認が必要です。

10. テーブルが改ページ境界で切断される

長いテーブルがページをまたぐとき、行の途中で切断されたり、ヘッダーが次ページに引き継がれなかったりします。

@media print {
  table { page-break-inside: auto; }
  tr { page-break-inside: avoid; }
  thead { display: table-header-group; } /* 各ページでヘッダーを繰り返す */
}

こうしたCSS印刷の問題をさらに深く理解するには、HTML→PDF CSSスニペット集が参考になります。改ページ制御、@page ルール、日本語フォント対応など、実務で必要なテクニックを網羅しています。

移行チェックリスト

window.print() からPDF生成APIへの移行を検討する際に確認すべき項目をまとめました。

移行前の確認

  • 現在 window.print() を使っている箇所をすべて洗い出す
  • 各箇所のPDF生成要件を整理する(品質、バッチ、自動化など)
  • 既存の @media print CSSを確認し、API移行後も活用できるか検討する
  • サーバーサイド生成が必要か、クライアントサイドからのAPI呼び出しで十分か判断する

移行時の対応

  • PDFテンプレートをHTMLで作成する(既存の @media print CSSを流用可能)
  • APIキーを環境変数で管理する(ハードコーディング禁止)
  • エラーハンドリングを実装する(API障害時のフォールバック)
  • 生成したPDFの保存先を決定する(S3、ローカルストレージなど)

移行後の確認

  • 出力PDFの品質をブラウザ印刷と比較する
  • 複数テンプレートでテストする
  • バッチ処理のパフォーマンスを確認する
  • エラー時の挙動を確認する

詳しい移行手順はPuppeteerからPDF APIへの移行ガイドも参考になります。

パフォーマンスとコストの比較

生成速度

方法 初回生成 2回目以降 バッチ100件
window.print() 即時(ダイアログ表示) 即時 不可
PDF生成API 1-3秒 1-3秒 数分(並列処理可)
Puppeteer(自己ホスト) 2-5秒 1-3秒 ブラウザメモリに依存

運用コスト

方法 インフラ 開発コスト 保守コスト
window.print() なし 低(CSS調整のみ) 中(ブラウザ更新で崩れる)
PDF生成API なし(マネージド) 低(API連携のみ)
Puppeteer(自己ホスト) 要サーバー 高(Chrome管理含む) 高(メモリリーク対策等)

Puppeteerとの詳しい比較はPDF APIとライブラリの比較で確認できます。

よくある質問

window.print()で生成したPDFの品質が十分なのに、APIに移行する必要がありますか?

品質が要件を満たしていて、サーバーサイド生成やバッチ処理が不要なら、window.print() のままで問題ありません。APIへの移行は、自動化や一貫性が必要になったタイミングで検討すれば十分です。

PDF生成APIは既存のHTML/CSSをそのまま使えますか?

はい。FUNBREW PDFはChromiumベースのレンダリングエンジンを使用するため、ブラウザで表示しているHTML/CSSをそのまま渡せます。@media print のスタイルも適用されます。

window.print()とPDF生成APIを併用できますか?

できます。たとえば、ユーザーが手動で印刷するケースは window.print() のまま残し、バックエンドの自動生成だけAPIに移行するハイブリッド構成も現実的です。

APIの障害時にwindow.print()にフォールバックできますか?

クライアントサイドからAPIを呼び出している場合は、エラー時に window.print() にフォールバックする実装が可能です。ただし、出力品質の違いについてユーザーに説明が必要になることがあります。

@media printのCSSはPDF生成APIでも必要ですか?

必須ではありませんが、活用できます。APIに渡すHTMLに @media print のスタイルが含まれていれば、そのスタイルが適用されます。API専用のCSSを別途書くこともできます。

バッチ生成で大量のPDFを作成するときのベストプラクティスは?

並列度を制御し、適切なリトライ処理を入れることが重要です。1,000件以上の場合はキューシステム(Redis、SQS等)と組み合わせて、レート制限を超えないようにしましょう。本番環境でのPDF運用ガイドが参考になります。

まとめ

window.print() はシンプルで強力なツールです。「このページを印刷する」だけの要件なら、これ以上のものは不要です。

しかし、以下の要件が1つでも当てはまるなら、PDF生成APIへの移行を検討すべきです:

  • サーバーサイドでの自動生成
  • ブラウザ間で一貫した出力
  • ページ番号・ヘッダー・フッターの制御
  • パスワード保護やウォーターマーク
  • バッチ処理・一括生成

FUNBREW PDFなら、HTML/CSSをそのまま使ってサーバーサイドでPDFを生成できます。既存のフロントエンド資産を活かしながら、window.print() の限界を超えられます。

Playgroundで試す →

関連記事

Powered by FUNBREW PDF