ガイド 第22

レンダリングパイプライン

DOM→PNG/PDF/.ai vs Satori/OG の使い分け

@event/visuals@event/asset-kit

@event/visuals は「asset-kit に渡す React 要素」を組み立てるだけで、実際のラスタライズは @event/asset-kit の capture が担う。ビジュアルを画像/PDF にする経路は 2 系統あり、用途で使い分ける。

経路 A: DOM → ラスタ(asset-kit /capture)— スタジオの既定

ブラウザで組んだ DOM をそのまま画像化する。modern-screenshot(foreignObject = ブラウザ自身のレンダラ)を第一候補にし、失敗時のみ html2canvas-pro に fallback する。html2canvas は CSS を再実装するため字形・行送り・グラデが画面と乖離するが、foreignObject 方式は画面と一致する。

captureCanvas(element, fmt, bg, config) の手順が事故対策そのもの:

  1. 画面外に native 寸法(transform: none)で mount — scale されたプレビュー直撮りはサブピクセル丸めで文字がズレる
  2. 2 フレーム待ち → document.fonts.ready
  3. 外部画像を data URI に inline 化 — CORS 無し画像は imgProxy 経由で fetch、リトライ 3 回 + 併走上限 6。本番でだけロゴ/顔写真が抜ける真因は serverless コールドスタートへの同時 fetch 殺到で、この併走制御が根治策
  4. img の load 完了を待ってから domToCanvasexportScale 倍で撮る

書き出し関数(全て client、ReactElement + ExportFmt を受ける):

await exportPng(el, fmt, bg, fileName);   // canvas → PNG ダウンロード
await exportPdf(el, fmt, bg, fileName);   // PNG(可逆) を pdf-lib で物理サイズ PDF に埋め込み
await exportAi(el, fmt, bg, fileName);    // 素の PDF を .ai + application/illustrator で保存
await copyPngToClipboard(el, fmt, bg);    // Figma へ Cmd+V(ClipboardItem に Promise<Blob>)
  • exportPdfPNG(可逆)で埋めるのは、JPEG だと低 opacity の透かし階調が潰れて「プレビューと違う」事故になるため。fmt.pageMm(既定 A4)で A1 等の大判も 1 ページで入稿できる
  • captureElementCanvas(source, fmt, bg)表示済み DOM を clone して撮る版。useLayoutEffect のテキストフィットが裏レンダだと間に合わず崩れるケース用

Format は ExportFmt 上位互換なので、visuals の Formatそのままこれらに渡せる:

await exportPng(<AnnounceCard input={input} fmt={fmt} brand={brand} />, fmt, brand.colors.bg, name);

適する場面: admin 主導・ピクセル完全一致・インタラクティブ(ブラウザに実フォントが載っていて、複雑な CSS を使える)。starter の /admin/visuals スタジオはこの経路。

経路 B: Satori / OG(server SVG → PNG)— per-request の共有カード

ブラウザが介在しない server 側で、React 要素を Satori が SVG 化 → PNG に焼く。next/ogImageResponse がこれ。リクエストごとに生成してキャッシュする用途(4S user → 個人シェアカード = 第24章、OGP 画像)に向く。

ただし制約が経路 A と根本的に違う:

  • Satori は CSS のサブセットしか解釈しない(flexbox 中心。grid・一部プロパティ非対応)。chrome/テンプレをそのまま流用できず、Satori 用に書き直す領域が要る
  • フォントファイルの同梱が必須(system-ui は使えない。woff を bundle して渡す)
  • 日本語の改行は自前で入れる必要があり、BudouX で文節分割(ZWSP 挿入)して渡すのが定石

これらの重い依存(bundled woff loader / budoux)は visuals では現状 deferredindex.ts にも「Satori/OG 経路で必要になった時点で追加する」と明記してある。参照実装は ivs-cms の src/lib/survey/fonts.ts(fontsourceWoff)と src/lib/jpSegment.ts(segmentJa)。

どちらを選ぶか

判断軸DOM→PNG/PDF(A)Satori/OG(B)
実行場所client(ブラウザ)server(edge/node)
きっかけadmin の書き出しボタンHTTP リクエスト(OGP 等)
忠実度画面と完全一致CSS サブセット近似
フォントブラウザ実フォントwoff 同梱必須
印刷 PDF/.ai対応(pageMm)非対応(PNG のみ)
向く用途会場パネル・SNS 告知の手動書き出し個人シェアカードの自動大量生成

原則: 人が 1 枚ずつ仕上げて入稿する印刷/SNS は経路 A、リクエストごとに自動生成してキャッシュする OG は経路 B