NaN/NaN/NaN

PuppeteerやPlaywrightでPDF生成を運用していると、Chromiumのバージョン管理、メモリ消費、並行処理の実装など、PDF生成そのものとは関係のない運用負荷が増えていきます。

この記事では、PuppeteerおよびPlaywrightベースのPDF生成をマネージドAPIに移行する具体的な方法と、移行によって得られるメリットを解説します。

Puppeteer / Playwright 運用の課題

PuppeteerやPlaywrightでPDF生成を本番運用する際、よく直面する問題があります。

メモリ消費

Chromiumは1プロセスあたり200〜500MBのメモリを消費します。同時に複数のPDFを生成する場合、メモリ使用量が急増しOOM(Out of Memory)でサーバーがクラッシュすることがあります。

Chromiumの管理

Puppeteer/Playwrightのバージョンアップに伴いChromiumのバージョンも変わります。CI/CDパイプラインでChromiumのダウンロードが失敗したり、OSのバージョンとの互換性問題が発生することもあります。

並行処理の実装

リクエストごとにpuppeteer.launch()playwright.chromium.launch()を呼ぶと遅く、ブラウザプールを自前で実装するのは複雑です。タブのリーク、ゾンビプロセスの処理、graceful shutdownなど、考慮すべきことが多くあります。

コールドスタート

サーバーレス環境(AWS Lambda等)ではChromiumの起動に数秒かかり、レイテンシの要件を満たせないことがあります。マネージドAPIならコールドスタートなしで利用できます。詳細はサーバーレス向けPDF API活用ガイドを参照してください。

これらの課題の技術的背景についてはwkhtmltopdf vs Chromiumで詳しく解説しています。

Before / After:コード比較

Before: Puppeteer(自前管理)

const puppeteer = require('puppeteer');

// ブラウザプールの管理が必要
let browser;

async function getBrowser() {
  if (!browser || !browser.isConnected()) {
    browser = await puppeteer.launch({
      headless: 'new',
      args: [
        '--no-sandbox',
        '--disable-setuid-sandbox',
        '--disable-dev-shm-usage',  // メモリ対策
        '--disable-gpu',
      ],
    });
  }
  return browser;
}

async function generatePdf(html) {
  const browser = await getBrowser();
  const page = await browser.newPage();

  try {
    await page.setContent(html, { waitUntil: 'networkidle0' });
    const pdf = await page.pdf({
      format: 'A4',
      margin: { top: '20mm', bottom: '20mm', left: '15mm', right: '15mm' },
      printBackground: true,
    });
    return pdf;
  } finally {
    await page.close();  // タブのリークを防ぐ
  }
}

// さらに必要なもの:
// - ブラウザプールの管理
// - タイムアウト処理
// - ゾンビプロセスの検知と再起動
// - メモリ監視
// - Chromiumのバージョン管理

After: マネージドAPI

async function generatePdf(html) {
  const response = await fetch('https://pdf.funbrew.cloud/api/v1/pdf/generate', {
    method: 'POST',
    headers: {
      'X-API-Key': process.env.FUNBREW_PDF_API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      html,
      options: {
        format: 'A4',
        margin: { top: '20mm', bottom: '20mm', left: '15mm', right: '15mm' },
        print_background: true,
      },
    }),
  });

  if (!response.ok) throw new Error(`API error: ${response.status}`);
  return Buffer.from(await response.arrayBuffer());
}

// 必要なもの: APIキーだけ
// ブラウザ管理、メモリ管理、並行処理はすべてAPI側が担当

コード量が大幅に削減され、Chromium関連の運用コードがすべて不要になります。

Playwright からの移行

Puppeteerではなく Playwright を使っている場合も移行方法は同じです。Playwright は Puppeteer よりも高機能ですが、PDF生成という用途では管理コストの高さは変わりません。

Before: Playwright(Node.js)

const { chromium } = require('playwright');

// Playwrightでも同様のブラウザ管理が必要
let browser;

async function getBrowser() {
  if (!browser) {
    browser = await chromium.launch({
      args: ['--no-sandbox', '--disable-setuid-sandbox'],
    });
  }
  return browser;
}

async function generatePdfWithPlaywright(html) {
  const browser = await getBrowser();
  const context = await browser.newContext();
  const page = await context.newPage();

  try {
    await page.setContent(html, { waitUntil: 'networkidle' });
    const pdf = await page.pdf({
      format: 'A4',
      margin: { top: '20mm', bottom: '20mm', left: '15mm', right: '15mm' },
      printBackground: true,
    });
    return pdf;
  } finally {
    await context.close();
  }
}

// インストールだけで数百MB:
// npx playwright install chromium

Before: Playwright(Python)

import asyncio
from playwright.async_api import async_playwright

async def generate_pdf_playwright(html: str) -> bytes:
    async with async_playwright() as p:
        # Chromium の起動に 1〜3 秒かかる
        browser = await p.chromium.launch()
        context = await browser.new_context()
        page = await context.new_page()

        await page.set_content(html, wait_until="networkidle")
        pdf = await page.pdf(
            format="A4",
            margin={"top": "20mm", "bottom": "20mm",
                    "left": "15mm", "right": "15mm"},
            print_background=True,
        )
        await browser.close()
        return pdf

# pip install playwright
# playwright install chromium  # 追加で数百MBのダウンロード

After: マネージドAPI(Playwright からも同じコードで移行可能)

Playwright からの移行も、API呼び出しのコードはPuppeteerからの移行と完全に同じです。ブラウザの種類に関わらず、HTTP呼び出し1本に集約されます。

# After: FUNBREW PDF API(Playwright からの移行でも同じ)
import os
import requests

def generate_pdf(html: str) -> bytes:
    resp = requests.post(
        "https://pdf.funbrew.cloud/api/v1/pdf/generate",
        headers={
            "X-API-Key": os.environ["FUNBREW_PDF_API_KEY"],
            "Content-Type": "application/json",
        },
        json={
            "html": html,
            "options": {
                "format": "A4",
                "margin": {"top": "20mm", "bottom": "20mm",
                           "left": "15mm", "right": "15mm"},
                "print_background": True,
            },
        },
        timeout=30,
    )
    resp.raise_for_status()
    return resp.content

Playwright 特有の waitUntil: 'networkidle' 相当の処理はAPIサーバー側が自動で行うため、明示的な指定は不要です。

コスト比較:セルフホスト vs マネージドAPI

「APIを使うとコストが上がるのでは」という懸念をよく聞きます。実際に計算してみましょう。

セルフホストのコスト内訳

PDF生成をPuppeteerで自前運用する場合のコストは以下の要素で構成されます。

サーバー費用

Chromiumは1プロセスあたり200〜500MBのメモリを消費します。月間1,000件のPDF生成を並行処理するには、最低でも2GBのRAMが必要です。

例: AWS t3.medium(2vCPU / 4GB RAM)
- オンデマンド: 約 $33/月
- ピーク時の追加インスタンス: 変動
- データ転送費: 変動

開発・メンテナンスコスト

初期実装(ブラウザプール + タイムアウト + リトライ): 8〜16時間
Chromium/Puppeteerのバージョンアップ対応: 2〜4時間/回(年3〜4回)
障害対応(OOM、ゾンビプロセス): 2〜8時間/回(月0〜2回)
年間メンテナンスコスト試算: エンジニア時間 20〜50時間

CI/CDコスト

Chromiumのダウンロードはビルド時間を増加させます。Docker imageが1GB以上になり、デプロイ時間も伸びます。

マネージドAPIのコスト

FUNBREW PDF 無料プラン: 月30件まで無料
有料プランの目安: 月間件数に応じて課金
メンテナンスコスト: ほぼゼロ(APIの変更対応のみ)

損益分岐点の目安

月間PDF生成件数と工数の関係(概算):

件数が少ない(〜100件/月):
  セルフホスト: サーバー $10〜30 + エンジニア時間
  API: ほぼ無料〜低コスト
  → APIが明らかに有利

件数が中程度(100〜10,000件/月):
  セルフホスト: サーバー $30〜100 + メンテナンス工数
  API: 件数に応じた従量課金
  → メンテナンス工数を考慮するとAPIが有利なケースが多い

件数が多い(10,000件以上/月):
  セルフホスト: 専用インフラが必要、運用チームも必要
  API: スケーリングはAPI側が対応
  → 要件次第だが、API専業チームのスケール効率には勝てない

重要な視点: コスト計算でよく見落とされるのが「エンジニアの機会コスト」です。Chromium管理に費やした時間は、プロダクトの機能開発には使えません。

Python / PHP 移行例(詳細版)

Python: httpx(非同期対応版)

requestsの同期版に加え、非同期処理が必要な場合はhttpxを使った実装も紹介します。

# httpx を使った非同期実装(FastAPI/Django Channels 等での利用に適している)
import os
import httpx

API_URL = "https://pdf.funbrew.cloud/api/v1/pdf/generate"
API_KEY = os.environ["FUNBREW_PDF_API_KEY"]

async def generate_pdf_async(html: str) -> bytes:
    async with httpx.AsyncClient(timeout=30.0) as client:
        response = await client.post(
            API_URL,
            headers={
                "X-API-Key": API_KEY,
                "Content-Type": "application/json",
            },
            json={
                "html": html,
                "options": {
                    "format": "A4",
                    "margin": {
                        "top": "20mm", "bottom": "20mm",
                        "left": "15mm", "right": "15mm",
                    },
                    "print_background": True,
                },
            },
        )
        response.raise_for_status()
        return response.content

# FastAPI での使用例
from fastapi import FastAPI
from fastapi.responses import Response

app = FastAPI()

@app.post("/invoice/pdf")
async def create_invoice_pdf(html: str):
    pdf_bytes = await generate_pdf_async(html)
    return Response(
        content=pdf_bytes,
        media_type="application/pdf",
        headers={"Content-Disposition": "attachment; filename=invoice.pdf"},
    )
# Django での使用例(同期)
import os
import requests
from django.http import HttpResponse

def generate_pdf_view(request):
    html = render_invoice_html(request)  # テンプレートからHTMLを生成

    resp = requests.post(
        "https://pdf.funbrew.cloud/api/v1/pdf/generate",
        headers={
            "X-API-Key": os.environ["FUNBREW_PDF_API_KEY"],
            "Content-Type": "application/json",
        },
        json={
            "html": html,
            "options": {"format": "A4", "print_background": True},
        },
        timeout=30,
    )
    resp.raise_for_status()

    return HttpResponse(
        resp.content,
        content_type="application/pdf",
        headers={"Content-Disposition": 'attachment; filename="invoice.pdf"'},
    )

PHP: Guzzle を使った実装

PHPアプリケーションではcurlの他に、Guzzle(Laravelや多くのPHPフレームワークで標準的に使われるHTTPクライアント)を使った実装も紹介します。

<?php
// Guzzle を使った実装(Laravel / Symfony などで推奨)
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;

class PdfGenerator
{
    private Client $client;
    private string $apiKey;

    public function __construct()
    {
        $this->client = new Client([
            'base_uri' => 'https://pdf.funbrew.cloud',
            'timeout'  => 30.0,
        ]);
        $this->apiKey = env('FUNBREW_PDF_API_KEY');
    }

    public function generate(string $html, array $options = []): string
    {
        try {
            $response = $this->client->post('/api/v1/pdf/generate', [
                'headers' => [
                    'X-API-Key'    => $this->apiKey,
                    'Content-Type' => 'application/json',
                ],
                'json' => [
                    'html'    => $html,
                    'options' => array_merge([
                        'format' => 'A4',
                        'margin' => [
                            'top'    => '20mm',
                            'bottom' => '20mm',
                            'left'   => '15mm',
                            'right'  => '15mm',
                        ],
                        'print_background' => true,
                    ], $options),
                ],
            ]);

            return (string) $response->getBody();
        } catch (RequestException $e) {
            $statusCode = $e->getResponse()?->getStatusCode() ?? 0;
            throw new \RuntimeException("PDF generation failed: HTTP {$statusCode}", $statusCode, $e);
        }
    }
}
<?php
// Laravel コントローラーでの使用例
use App\Services\PdfGenerator;
use Illuminate\Http\Response;

class InvoiceController extends Controller
{
    public function __construct(private PdfGenerator $pdfGenerator) {}

    public function download(Invoice $invoice): Response
    {
        $html = view('invoices.pdf', compact('invoice'))->render();
        $pdf  = $this->pdfGenerator->generate($html);

        return response($pdf, 200, [
            'Content-Type'        => 'application/pdf',
            'Content-Disposition' => "attachment; filename=\"invoice-{$invoice->id}.pdf\"",
        ]);
    }
}
<?php
// Before: spatie/browsershot(内部でNode.js + Puppeteerを呼び出す)
use Spatie\Browsershot\Browsershot;

function generatePdf(string $html): string
{
    return Browsershot::html($html)
        ->format('A4')
        ->margins(20, 15, 20, 15)
        ->showBackground()
        ->pdf();
    // 問題点: Node.js + Puppeteerがサーバーにインストールされている必要がある
    //         実行のたびにChromiumプロセスを起動するためメモリを消費する
}
<?php
// After: FUNBREW PDF API(PHP curl 版 — Node.js・Puppeteer不要)
function generatePdf(string $html): string
{
    $apiKey = getenv('FUNBREW_PDF_API_KEY');

    $ch = curl_init('https://pdf.funbrew.cloud/api/v1/pdf/generate');
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST           => true,
        CURLOPT_TIMEOUT        => 30,
        CURLOPT_HTTPHEADER     => [
            'X-API-Key: ' . $apiKey,
            'Content-Type: application/json',
        ],
        CURLOPT_POSTFIELDS => json_encode([
            'html'    => $html,
            'options' => [
                'format' => 'A4',
                'margin' => [
                    'top' => '20mm', 'bottom' => '20mm',
                    'left' => '15mm', 'right' => '15mm',
                ],
            ],
        ]),
    ]);

    $body   = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($status !== 200) {
        throw new \RuntimeException("PDF API error: HTTP $status");
    }

    return $body; // PDFバイナリ
}

// 依存: curlのみ(PHPデフォルト拡張)。Node.js・Puppeteer不要。

パフォーマンス比較

セルフホストのPuppeteer/PlaywrightとマネージドAPIの実測値の目安を紹介します(環境によって変動します)。

レスポンスタイム

シナリオ Puppeteer(コールドスタート) Puppeteer(ウォーム) マネージドAPI
シンプルなHTML(〜10KB) 3,000〜5,000ms 500〜800ms 300〜600ms
画像入りHTML(〜100KB) 4,000〜8,000ms 800〜1,500ms 500〜1,200ms
複雑なCSS + 画像 6,000〜15,000ms 1,500〜3,000ms 800〜2,000ms
サーバーレス(Lambda) 8,000〜15,000ms 300〜600ms

「コールドスタート」はChromiumの起動時間を含みます。ブラウザプールを維持する「ウォーム」状態でも、APIに比べてレスポンスは遅くなる傾向があります。

メモリ使用量

Puppeteer(ブラウザプール 3プロセス):
  アイドル時: 600MB〜1.5GB
  PDF生成中: 1GB〜2GB+
  ピーク時(10並行): 3GB+

マネージドAPI:
  アイドル時: <10MB(APIクライアントのみ)
  PDF生成中: <50MB(HTTPリクエスト処理のみ)
  ピーク時: スケーリングなし(100並行でもほぼ同じ)

Docker イメージサイズ

# Before: Puppeteer入りイメージ
FROM node:20
RUN apt-get install -y chromium   # +300MB
RUN npm install puppeteer          # +200MB
# 合計: 1GB以上

# After: APIクライアントのみ
FROM node:20-alpine
# chromium不要
# 合計: ~150MB

デプロイ時間の短縮と、コンテナレジストリのストレージ削減も見込めます。

同時リクエスト処理

Puppeteer(ブラウザプール、2GBのサーバー):
  最大同時処理: 3〜5件
  それ以上は: キューイング or OOM

マネージドAPI:
  最大同時処理: プランによるが実用上制限なし
  スケーリング: API側が自動処理

移行手順

Step 1: API動作確認

まずPlaygroundでHTMLを貼り付けて、出力結果がPuppeteerと同等かを確認します。無料プラン(月30件)で実際のAPIレスポンスもテストできます。

Step 2: PDF生成関数を差し替え

既存のPuppeteer呼び出し部分をAPI呼び出しに置き換えます。各言語のコード例はクイックスタートガイドを参照してください。

Step 3: テンプレートの移行

Puppeteerで文字列連結やテンプレートリテラルで組み立てていたHTMLは、テンプレートエンジンに移行するとさらに管理が楽になります。

// Before: 文字列連結
const html = `<h1>請求書</h1><p>${customerName}</p>...`;
const pdf = await generatePdfWithPuppeteer(html);

// After: テンプレート + API
const response = await fetch('https://pdf.funbrew.cloud/api/v1/pdf/from-template', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.FUNBREW_PDF_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    template: 'invoice',
    variables: { customer_name: customerName, total: '165,000' },
  }),
});

Step 4: Puppeteerの依存を削除

移行が完了したら、puppeteerまたはplaywrightパッケージとChromium関連の設定を削除します。

# Puppeteer の場合
npm uninstall puppeteer

# Playwright の場合
npm uninstall playwright @playwright/test

# Dockerfileからchromiumのインストール行を削除
# CI/CDのChromiumキャッシュ設定を削除

移行チェックリスト

移行をスムーズに進めるための確認事項をまとめました。

移行前
  [ ] Playground でHTMLを貼り付けて出力品質を確認
  [ ] 無料プラン(月30件)でAPIキーを取得
  [ ] 環境変数 FUNBREW_PDF_API_KEY を設定
  [ ] PDF生成関数を新しいAPI呼び出しに置き換え
  [ ] ステージング環境で既存のPDF出力と目視比較

CSS確認
  [ ] @media print / @media screen の使い分けを確認
  [ ] ページブレーク(page-break-before/after)の動作確認
  [ ] 外部フォント(Google Fonts等)をインラインCSS or Base64に変換済みか確認
  [ ] 背景色・背景画像が印刷設定で表示されるか確認

本番移行
  [ ] エラーハンドリング(HTTPステータスコードに応じたリトライ・アラート)を実装
  [ ] タイムアウト設定を追加(推奨: 30秒)
  [ ] Puppeteer/Playwrightパッケージをアンインストール
  [ ] Dockerfileから Chromium/Node.js インストール行を削除
  [ ] CI/CDのChromiumキャッシュ設定を削除
  [ ] サーバーのメモリ割り当てを見直し(Chromiumが不要になるため削減可能)

移行時によくある問題と解決策

実際の移行でよく発生する問題とその対処法を紹介します。

問題1: 外部リソース(画像・フォント)が表示されない

原因: APIサーバーがローカルネットワーク上のURLや localhost にアクセスできない。

解決策: 画像はBase64に変換してHTMLに埋め込むか、公開URLを使用します。

const fs = require('fs');
const path = require('path');

// ローカル画像をBase64に変換してHTMLに埋め込む
function embedLocalImage(imagePath) {
  const ext  = path.extname(imagePath).slice(1); // 'png', 'jpg' など
  const data = fs.readFileSync(imagePath).toString('base64');
  return `data:image/${ext};base64,${data}`;
}

const html = `
  <img src="${embedLocalImage('./logo.png')}" alt="ロゴ" />
  <p>ローカル画像をBase64で埋め込む例</p>
`;

問題2: フォントが適用されない

原因: Google Fontsなど外部フォントの読み込みがタイムアウトする、またはAPIサーバーからアクセスできない。

解決策: フォントをBase64でHTMLに埋め込みます。日本語フォントはFUNBREW PDFにプリインストール済みのため追加対応は不要です。

<style>
  /* Google FontsのURLをそのまま使う場合(外部アクセスが許可されている場合) */
  @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP&display=swap');

  /* またはBase64で埋め込む(確実な方法) */
  @font-face {
    font-family: 'MyFont';
    src: url('data:font/woff2;base64,d09GMgAB...') format('woff2');
  }

  body { font-family: 'Noto Sans JP', sans-serif; }
</style>

問題3: @media screen のスタイルが反映されない

原因: Puppeteerで page.emulateMediaType('screen') を使っていた場合、APIはデフォルトで print メディアタイプを使用します。

解決策: CSSを @media print に統一するか、既存のスタイルを @media print { ... } で囲んで明示的に適用させます。

/* Before: screen想定のスタイル(APIでは無視される) */
@media screen {
  .invoice { padding: 40px; background: #fff; }
}

/* After: print向けに明示 */
@media print {
  .invoice { padding: 40px; background: #fff; }
}

/* または両方に対応 */
.invoice { padding: 40px; background: #fff; }

問題4: タイムアウトエラーが発生する

原因: 複雑なHTMLや大量の画像を含む場合、APIのレスポンスに時間がかかることがあります。

解決策: クライアント側のタイムアウトを30秒以上に設定し、リトライロジックを実装します。

async function generatePdfWithRetry(html, options = {}, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const controller = new AbortController();
      const timeoutId  = setTimeout(() => controller.abort(), 30_000); // 30秒

      const response = await fetch('https://pdf.funbrew.cloud/api/v1/pdf/generate', {
        method: 'POST',
        headers: {
          'X-API-Key': process.env.FUNBREW_PDF_API_KEY,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ html, options }),
        signal: controller.signal,
      });

      clearTimeout(timeoutId);

      if (response.status === 429 && attempt < maxRetries) {
        // レート制限: 指数バックオフで待機してリトライ
        await new Promise(r => setTimeout(r, 2 ** attempt * 1000));
        continue;
      }

      if (!response.ok) throw new Error(`API error: ${response.status}`);
      return Buffer.from(await response.arrayBuffer());
    } catch (err) {
      if (attempt === maxRetries) throw err;
      await new Promise(r => setTimeout(r, 1000 * attempt));
    }
  }
}

問題5: Playwright の waitForSelector に相当する動作が必要

原因: Puppeteer/Playwright で動的コンテンツのレンダリング完了を待つためにセレクタ待機を使っていた場合。

解決策: 動的コンテンツはサーバーサイドレンダリング(SSR)でHTMLを確定させてからAPIに渡すのがベストです。JavaScriptのレンダリングが必要な場合は、フレームワーク(Next.js等)のSSR機能を活用してください。

// Before: Playwright でセレクタを待つ
await page.waitForSelector('.invoice-total');
const pdf = await page.pdf();

// After: サーバーサイドで完全なHTMLを生成してからAPIに渡す
const html = await renderInvoiceServerSide(invoiceData); // SSRで確定したHTML
const pdf = await generatePdf(html); // 追加の待機は不要

問題6: Puppeteer の page.evaluate() で動的に値を変更していた

原因: page.evaluate() でDOMを操作してからPDF生成していた場合、APIでは同じことができません。

解決策: DOM操作の内容をHTMLテンプレートに組み込み、サーバーサイドで値を埋め込んだHTMLを生成してAPIに渡します。

// Before: page.evaluate() でDOMを操作
await page.evaluate((total) => {
  document.querySelector('#total').textContent = total;
}, '165,000円');
const pdf = await page.pdf();

// After: HTMLテンプレートに値を埋め込む
const html = `
  <div id="total">${total}</div>
`;
const pdf = await generatePdf(html);

移行による改善ポイント

項目 Puppeteer/Playwright(自前) マネージドAPI
メモリ使用量 200〜500MB/プロセス ほぼゼロ(HTTP呼び出しのみ)
コールドスタート 1〜3秒 なし(常時稼働)
並行処理 自前実装が必要 API側で自動スケーリング
Chromium管理 バージョン管理・互換性対応 不要
サーバーレス対応 Lambdaレイヤー等の工夫が必要 問題なし
Docker image 1GB以上(Chromium込み) Chromium不要で軽量化
コールドスタート(Lambda) 8〜15秒 300〜600ms

CSSの互換性

FUNBREW PDFのqualityエンジンはChromiumベースのため、Puppeteer/Playwrightと同等のCSS対応を持ちます。ほとんどの場合、HTMLをそのまま移行できます。CSS固有の問題への対処法はHTML to PDF CSSトラブルシューティングをご覧ください。

注意点として、page.emulateMediaType('screen')を使っていた場合、APIではデフォルトがprintメディアタイプです。CSS内で@media print@media screenの指定を確認してください。

セキュリティの向上

Puppeteer/Playwrightの自前運用ではSSRFやChromiumの脆弱性への対応が必要でしたが、マネージドAPIではこれらがサービス側で管理されます。詳しくはPDF APIセキュリティガイドをご覧ください。

まとめ

PuppeteerまたはPlaywrightからマネージドAPIへの移行は、PDF生成の品質を維持しながら運用負荷を大幅に削減できます。

  • コード量の削減: ブラウザ管理・プール・タイムアウト処理が不要に
  • インフラの軽量化: Chromiumが不要になりDockerイメージが軽量に
  • スケーラビリティ: 並行処理をAPI側に任せられる
  • 安定性: OOMやゾンビプロセスの心配がなくなる
  • コスト最適化: メモリ削減によりサーバーコストを削減できる

まずはPlaygroundで現在のHTMLを試し、出力品質を確認してみてください。無料プランで本番移行前のテストも可能です。API仕様の詳細はドキュメントを参照してください。

移行後の本番運用についてはPDF API本番運用チェックリスト、エラー処理についてはPDF APIエラーハンドリング完全ガイドが役立ちます。

関連リンク

Powered by FUNBREW PDF