サーバーサイドレンダリング

DOM に依存しない Canvas2D 契約を介してラインおよび株価チャートをレンダリングします。

@sixtyfold/ssr は最小限のサーバーキャンバス上で同じ TypeScript レンダラーを実行します。DOM のキャンバスグローバルをインポートせず、ブラウザ Worker を作成せず、アニメーションも行いません。実際のラインおよび株価の PNG 出力は Node.js、Bun、Deno で継続的に検証されています。

SSR パッケージと、実行環境に適した Canvas2D 実装をインストールしてください。検証済み互換性セットは @napi-rs/canvas を使用しますが、Sixtyfold はそれをライブラリ依存関係として強制しません。

生成されたリファレンスは、正確なSSR関数、レンダーオプション、およびランタイム非依存のキャンバスタイプを一覧表示します。

pnpm add @sixtyfold/ssr@1 @napi-rs/canvas
import { createCanvas } from "@napi-rs/canvas";
import { renderLineChartSSR } from "@sixtyfold/ssr";

const width = 1200;
const height = 630;
const canvas = createCanvas(width * 2, height * 2);

renderLineChartSSR(
  canvas,
  data,
  {
    animated: false,
    chartBackground: "#081018",
    series: [{ color: "#65d6ff", width: 2 }],
    axis: { bottom: { format: "time" } },
  },
  { width, height, dpr: 2 },
);

const png = await canvas.encode("png");

キャンバス実装が画像エンコーディングを所有します。

検証済みのサーバーランタイム

互換性スイートは現在、これらのCIターゲットを固定しています:

ランタイム検証済みバージョンCanvas2D バックエンド
Node.js22.22.3@napi-rs/canvas 1.0.2
Bun1.3.14@napi-rs/canvas 1.0.2
Deno2.9.3@napi-rs/canvas 1.0.2

これらは再現可能なCIターゲットであり、最小バージョンの主張ではありません。各ランタイムはネットワークアクセスなしで決定論的なLineおよびStockフィクスチャをレンダリングします。ゲートはプロットのピクセルを固定されたビジュアル参照と比較し、指定された各ラベルが境界領域内の正確なレイアウトアンカーで可視インクを生成することを要求します。またPNG署名、寸法、およびエンコード済み出力を検証します。フォント依存のグリフ幅やインクリレーションは固定しません。PNGは検査可能なCIアーティファクトとして保持されます。リリース前には、Nodeのビジュアル参照をUbuntu、macOS、およびWindowsで繰り返し確認します。」}}}ulkulu.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz.gz

Node.js と Bun は同じモジュールを直接実行します:

node app.mjs
bun app.mjs

Deno はローカルの node_modules ツリーと、Node-API Canvas アドオン用の明示的な権限を必要とします:

deno run \
  --allow-read \
  --allow-env \
  --allow-ffi \
  --allow-sys=homedir \
  --node-modules-dir=manual \
  app.mjs

アプリケーションがエンコード済み画像をディスクに書き込む場合にのみ --allow-write=<output-directory> を追加してください。実行時検証ソース 、、Bun の Node-API サポート 、、および Deno ネイティブアドオンガイダンス 、 を参照してください。

サポートされている構成

SSRはレンダラー向けのビジュアルオプションを受け入れます: padding、direction、grid、axes、background、範囲セレクター、tooltip style、crosshair style、labels、overlays、line series/詳細度、stock candles、indicators、volume profile、price lines、およびmarkers。ブラウザ専用のコールバック、DOMコントロール、ポインター操作、およびWorkersは意図的に含まれていません。

1.0.1のヘルパーはチャートレベルのラインmarkerスタイリングと、stockのtimeScale、showLeftPriceMarker、およびshowRightPriceMarkerオプションを共有レンダラーへ転送します。静的なstock出力で欠落観測値を圧縮するにはtimeScale: "market"を使用してください。明示的なaxis.left.cursorLabel.visibleまたはaxis.right.cursorLabel.visible設定は対応するprice-markerエイリアスより優先されます。静的出力はホバーイベントやカーソル移動を生成しません。

静的レンダリングは即時完了しますが、明確にするためにanimated: falseを設定してください。スナップショットの差異を防ぐために、テストと本番で同じwidth、height、およびdpr契約を使用してください。

ランタイムに依存しないキャンバス注入

呼び出し側がすでにキャンバスを所有していない場合、createCanvas を SSRRenderOptions に渡してください。これはバックイングストアの寸法を受け取ります。返却されるオブジェクトはレンダラーが使用する Canvas2D メソッドを含む width、height、および getContext("2d") を備えている必要があります。

決定論的な出力

  • レンダリングイメージ内でキャンバス実装とフォントを固定してください。
  • サーバーがブラウザのフォントスタックを提供しない場合は、明示的なフォントファミリを指定してください。
  • リクエストのレンダリング中にリモートURLオーバーレイを避け、レンダリング前にアセットを解決してキャッシュしてください。
  • 入力配列、チャートのサイズ、DPR、ロケール、タイムゾーン、およびオプションを安定させてください。