SCALE — Build Lab
開発パターン · TYPESCRIPT PATTERN

PDFエクスポートパターン

CATEGORY開発パターン TYPETypeScript Pattern EFFORT180〜480分 DIFFICULTY
PRIMARY CODE
tsx
// クライアントサイドPDF生成(@react-pdf/renderer 不要・印刷API利用)
export async function exportToPDF(elementId: string, filename = 'report.pdf') {
  const el = document.getElementById(elementId);
  if (!el) throw new Error(`Element not found: #${elementId}`);

  // 印刷用 iframe を生成
  const iframe = document.createElement('iframe');
  iframe.style.cssText = 'position:fixed;right:0;bottom:0;width:0;height:0;border:0';
  document.body.appendChild(iframe);

  const doc = iframe.contentWindow!.document;
  doc.write(`<!DOCTYPE html><html><head><title>${filename}</title>`);
  // 親ページのスタイルを継承
  document.querySelectorAll('link[rel="stylesheet"], style').forEach((s) => {
    doc.head.appendChild(s.cloneNode(true));
  });
  doc.write(`</head><body>${el.outerHTML}</body></html>`);
  doc.close();

  iframe.contentWindow!.focus();
  iframe.contentWindow!.print();

  setTimeout(() => document.body.removeChild(iframe), 1000);
}

// サーバサイド PDF(Cloudflare Workers + Browser Rendering)
export async function generatePDFServer(env: { BROWSER: any }, html: string): Promise<ArrayBuffer> {
  const browser = await env.BROWSER.launch();
  const page = await browser.newPage();
  await page.setContent(html);
  const pdf = await page.pdf({ format: 'A4', printBackground: true });
  await browser.close();
  return pdf;
}

// 使い方:
// <button onClick={() => exportToPDF('report-area', '月次レポート.pdf')}>PDF出力</button>
前提条件
Tailwind CSS v4TypeScript 5
USE CASES
  • レポート出力
  • 請求書PDF
  • 契約書PDF

PDFエクスポートパターン

:LiTarget: 用途

HTMLレポートをPDFに変換してダウンロード/メール送信するパターン。

:LiSparkle: 特徴

  • HTML→PDF変換
  • スタイル保持
  • メール送信
  • ダウンロード

:LiCode: コード(コピペ用)

// クライアントサイドPDF生成(@react-pdf/renderer 不要・印刷API利用)
export async function exportToPDF(elementId: string, filename = 'report.pdf') {
  const el = document.getElementById(elementId);
  if (!el) throw new Error(`Element not found: #${elementId}`);

  // 印刷用 iframe を生成
  const iframe = document.createElement('iframe');
  iframe.style.cssText = 'position:fixed;right:0;bottom:0;width:0;height:0;border:0';
  document.body.appendChild(iframe);

  const doc = iframe.contentWindow!.document;
  doc.write(`<!DOCTYPE html><html><head><title>${filename}</title>`);
  // 親ページのスタイルを継承
  document.querySelectorAll('link[rel="stylesheet"], style').forEach((s) => {
    doc.head.appendChild(s.cloneNode(true));
  });
  doc.write(`</head><body>${el.outerHTML}</body></html>`);
  doc.close();

  iframe.contentWindow!.focus();
  iframe.contentWindow!.print();

  setTimeout(() => document.body.removeChild(iframe), 1000);
}

// サーバサイド PDF(Cloudflare Workers + Browser Rendering)
export async function generatePDFServer(env: { BROWSER: any }, html: string): Promise<ArrayBuffer> {
  const browser = await env.BROWSER.launch();
  const page = await browser.newPage();
  await page.setContent(html);
  const pdf = await page.pdf({ format: 'A4', printBackground: true });
  await browser.close();
  return pdf;
}

// 使い方:
// <button onClick={() => exportToPDF('report-area', '月次レポート.pdf')}>PDF出力</button>

:LiHandPointer: 使い方

対象プロジェクトに該当ファイルをコピーして、props を流し込むだけ。

:LiAlertCircle: 注意事項

  • 依存パッケージを忘れず追加