mcpField
P
pdf-generation

pdf-generation

github.com/claude-dev-suiteドキュメント4674 回インストール4 日前に更新
リポジトリ

pdf-generation について

Node.js、React、Python での PDF 生成手法を扱う実践スキルです。複数のライブラリを使う方法と、本番環境での信頼性やリソース管理の注意点をまとめています。

主な機能

  1. 01Puppeteer による HTML から PDF への変換
  2. 02PDFKit によるプログラム生成
  3. 03@react-pdf/renderer による React コンポーネントの変換
  4. 04ReportLab による Python PDF 生成
  5. 05本番向けチェックリストとアンチパターン

活用シーン

  1. 01複雑な HTML レイアウトの PDF 化
  2. 02アプリケーションデータからの請求書 PDF 作成
  3. 03React コンポーネントからの PDF 生成
  4. 04本番環境での PDF 生成計画

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/claude-dev-suite/claude-dev-suite --skill pdf-generation

インストール後、Claude Code で/pdf-generationを入力して読み込まれたか確認します。

詳細

PDF 生成

Puppeteer による HTML から PDF への変換(複雑なレイアウトにおすすめ)

import puppeteer from 'puppeteer';

async function generatePdf(html: string): Promise<Buffer> {
  const browser = await puppeteer.launch({ headless: true });
  const page = await browser.newPage();
  await page.setContent(html, { waitUntil: 'networkidle0' });

  const pdf = await page.pdf({
    format: 'A4',
    margin: { top: '20mm', bottom: '20mm', left: '15mm', right: '15mm' },
    printBackground: true,
  });

  await browser.close();
  return Buffer.from(pdf);
}

// Express endpoint
app.get('/api/invoice/:id/pdf', async (req, res) => {
  const invoice = await getInvoice(req.params.id);
  const html = renderInvoiceHtml(invoice);
  const pdf = await generatePdf(html);
  res.contentType('application/pdf');
  res.setHeader('Content-Disposition', `attachment; filename="invoice-${invoice.number}.pdf"`);
  res.send(pdf);
});

PDFKit(Node.js — プログラムによる生成)

import PDFDocument from 'pdfkit';

function createInvoicePdf(invoice: Invoice): Promise<Buffer> {
  return new Promise((resolve) => {
    const doc = new PDFDocument({ size: 'A4', margin: 50 });
    const chunks: Buffer[] = [];
    doc.on('data', (chunk) => chunks.push(chunk));
    doc.on('end', () => resolve(Buffer.concat(chunks)));

    // Header
    doc.fontSize(20).text('INVOICE', { align: 'right' });
    doc.fontSize(10).text(`#${invoice.number}`, { align: 'right' });
    doc.moveDown();

    // Table
    doc.fontSize(12).text('Item', 50, doc.y);
    doc.text('Amount', 400, doc.y - 12, { width: 100, align: 'right' });
    doc.moveTo(50, doc.y).lineTo(550, doc.y).stroke();

    for (const item of invoice.items) {
      doc.text(item.description, 50, doc.y + 5);
      doc.text(`$${item.amount.toFixed(2)}`, 400, doc.y - 12, { width: 100, align: 'right' });
    }

    doc.end();
  });
}

@react-pdf/renderer(React コンポーネントから PDF への変換)

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

const styles = StyleSheet.create({
  page: { padding: 30 },
  title: { fontSize: 24, marginBottom: 20 },
  row: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 5 },
});

function InvoicePdf({ invoice }: { invoice: Invoice }) {
  return (
    <Document>
      <Page size="A4" style={styles.page}>
        <Text style={styles.title}>Invoice #{invoice.number}</Text>
        {invoice.items.map((item) => (
          <View key={item.id} style={styles.row}>
            <Text>{item.description}</Text>
            <Text>${item.amount.toFixed(2)}</Text>
          </View>
        ))}
      </Page>
    </Document>
  );
}

// Server-side render
const buffer = await renderToBuffer(<InvoicePdf invoice={invoice} />);

Python(ReportLab)

from reportlab.lib.pagesizes import A4
from reportlab.platypus import SimpleDocTemplate, Table, Paragraph
from reportlab.lib.styles import getSampleStyleSheet

def generate_invoice_pdf(invoice: dict, output_path: str):
    doc = SimpleDocTemplate(output_path, pagesize=A4)
    styles = getSampleStyleSheet()
    elements = []

    elements.append(Paragraph(f"Invoice #{invoice['number']}", styles['Title']))

    data = [['Item', 'Amount']]
    for item in invoice['items']:
        data.append([item['description'], f"${item['amount']:.2f}"])

    table = Table(data, colWidths=[350, 100])
    elements.append(table)
    doc.build(elements)

アンチパターン

アンチパターン対策
PDF リクエストごとに新しいブラウザーを起動するブラウザーインスタンスを再利用し、新しいページを作成します
PDF 生成にタイムアウトを設定しないpage.setDefaultTimeout() を設定し、中断できるようにします
リクエスト処理中に PDF を同期的に生成するバックグラウンドジョブキューを使用します
HTML テンプレートに CSS をインラインで記述する埋め込みの <style> または printBackground: true を使用します
メモリーを解放しない使用後にページを閉じ、同時生成数を制限します

本番環境への移行チェックリスト

  • ブラウザーインスタンスのプーリング(Puppeteer/Playwright)
  • バックグラウンドキューでの PDF 生成
  • メモリー制限とタイムアウトの設定
  • 生成した PDF をオブジェクトストレージ(S3)に保存
  • ダウンロード用の Content-Disposition ヘッダー
  • アーカイブが必要な場合の PDF/A 準拠

コメント