← シリーズに戻る

版と cache-control で戦略が割れる — iframe 型は版を切らせない、DOM 直挿し型は必須にする

Part 4 / 6 script 1行で配るJS SDKの設計 — 実在5製品を分解する

前記事でプロトコル契約を扱いました。プロトコルを固定したら、次は「顧客にどの版を使わせるか」です。

5製品を並べます。iframe 型と DOM 直挿し型で、戦略が真っ二つに割れました。

実測 cache-control 行列

2026-08-08 に curl -I で取ったヘッダーです。

製品URLcache-control
Safie loadersafie.link/sdk/js/api/v1/latest/no-cache
Safie iframe body.../lib/main-5DQ7WBMB.js (ハッシュ URL)(無し = デフォルト、実質永久)
YouTube loaderyoutube.com/iframe_apiprivate, max-age=0
YouTube widgetapi body.../s/player/854a788e/www-widgetapi.vflset/...public, max-age=31536000
Vimeoplayer.vimeo.com/api/player.jspublic, max-age=1800
Mux Player @latestcdn.jsdelivr.net/npm/@mux/mux-player@latestpublic, max-age=604800, s-maxage=43200
Mux Player @3.7.0.../mux-player@3.7.0public, max-age=31536000, immutable
Video.js /8.23.4/vjs.zencdn.net/8.23.4/video.min.js(無し = CDN デフォルト)
Video.js /8/vjs.zencdn.net/8/video.min.js403 Forbidden

2軸を読む

軸1: 顧客が版を選べるか

  • 選べる: Mux Player (@3.7.0 等 semver 指定必須)、Video.js (/8.23.4/ 必須、/8/ は 403)
  • 選べない: Safie (/latest/)、YouTube (/iframe_api に版無し)、Vimeo (/api/player.js に版無し)

軸2: どこで版を固定するか

  • URL に版を含める: Video.js (/8.23.4/)、Mux Player (@3.7.0)
  • URL に無いが内部で hash 済み: YouTube (loader が 854a788e というビルドハッシュ URL を吐き出す)
  • URL に版なし、内容が動く: Safie loader (/latest/)、Vimeo (/player.js)

iframe 型の3製品は「顧客に版を選ばせない」で揃い、DOM 型の2製品は「exact version を強制」で揃います。偶然ではありません。技術的な必然があります。

iframe 型が版を固定させない理由

iframe 型 SDK は親 (顧客サイトに刺さる loader) と 子 (自社ドメインで動く iframe 本体) に分かれ、postMessage で会話しています。プロトコルは時間とともに拡張されます。メソッドが増え、フィールドが増え、ハンドシェイクが改良されます。親子でバージョンがズレると通信が壊れます。

だから顧客に loader の版を固定させられません。

古い loader を貼った顧客のために新プロトコルを我慢すると機能追加が止まります。逆に古い loader を切り捨てると、顧客側の HTML 修正が発生し、営業的に大問題です。板挟みを最初から避ける唯一の方法が「loader URL は固定、中身は自社で切り替える」。

Safie が典型的で、safie.link/sdk/js/api/v1/latest/no-cache で毎回取り直させます。中身が更新されれば顧客の JS 挙動も自動的に前進します。

YouTube は少し変わっています。loader (iframe_api) は同じく毎回取り直させますが、その loader が 854a788e のような build hash 付き URL を吐き出す構造。build hash が変わればブラウザキャッシュを跨いで新版が引かれます。二段間接で、中間キャッシュ (企業プロキシ等) が loader を長期保持しても body の hash 経由で更新が届きます。

Vimeo は中間の妥協点です。/api/player.jsmax-age=1800 (30分) にしています。プロトコル変更を出すと最大30分は古い版が動く猶予期間があり、その後は自動的に切り替わります。iframe 型なのに latest を明示しないのは、Vimeo のプロトコルが Vimeo Player SDK として独立ドキュメント化されていて、外部開発者が自作クライアントを書ける前提のためと読めます。

DOM 直挿し型が exact version を強制する理由

Mux Player と Video.js は顧客サイトの DOM に直接組み込まれます。プロトコルはありません。HTTP や WebSocket で自社サーバーと直接会話するだけです。親子ハンドシェイクを揃える必要が構造的に無いので、iframe 型が抱える板挟みが最初から存在しません。

代わりに別の問題があります。SDK バンドルの API 変更です。videojs(id, options) の options 形式が変わったり、プラグイン API が刷新されたりすると、顧客が自前で書いた JS が壊れます。SDK 側は「exact version 固定」を強制することで、顧客がアップグレードするタイミングを自分で選べるようにします。

  • Mux Player は npm パッケージなので、@3.7.0 のように semver 指定が自然。jsdelivr の CDN が @3.7.0immutable で1年キャッシュします。
  • Video.js は vjs.zencdn.net/8.23.4/ を必須にし、/8/403 で拒否します。「メジャーバージョンだけ書いておいて勝手にマイナーアップグレード」ができない。

DOM 型は顧客の自由度と引き換えに、アップグレードの責任を顧客側に置きます。iframe 型と真逆の哲学です。

どちらが正しいかではありません。iframe 型は「変化を強制する自由」、DOM 型は「変化を許可制にする自由」を売っている、と読むほうが正確です。

no-cachemax-age=0 の細かい違い

Safie の loader は no-cache、YouTube の loader は private, max-age=0 です。挙動はほぼ同じですが微妙に違います。

  • no-cache: キャッシュしていいが、使う前に必ずサーバーに再検証 (If-None-Match で 304 を狙う)
  • max-age=0: そもそも新鮮期間ゼロなので、CDN によっては再検証も飛ばして毎回フル取得

実装差というより意図の表現の違いで、どちらも「loader を絶対にキャッシュで固定させない」宣言です。

iframe 型 SDK の loader はバージョニング契約そのものです。キャッシュしたら契約が過去のまま止まります。

自作するときの選択マトリクス

iframe 型 SDK を作るDOM 直挿し型 SDK を作る
loader URL/latest/ 固定、no-cache版付き必須、immutable で長期キャッシュ
版切り替え自社側で完結、顧客は貼り替え不要顧客が明示的にアップグレード
プロトコル互換性気にしないでいい (常に同期)気にしないでいい (プロトコルが無い)
破壊的変更のコスト自社側で受ける (旧顧客対応込み)顧客側で受ける (アップグレード拒否可)
顧客への説明「script 1行貼るだけ、更新は自動」「semver に従って明示アップグレード」

DOM 型に見えて iframe を1つ持つハイブリッド、iframe 型に見えて内部で version pinning できる例外もありますが、5製品の実測は綺麗に二分します。SDK の埋め込み形態を決めた時点で、cache-control 戦略は半分決まっていると考えていい設計です。

次: 初期化スタイルと二重ロード耐性

版が決まったら、次は「顧客が SDK をどう呼び出すか」の入口設計です。命令的 (new YT.Player(...))、data 属性 (<video data-setup="...">)、Web Components (<mux-player>) の3種があり、5製品が3つに割れています。もう一つ、5製品全社が本番で踏んでいる「二重ロード」問題を扱います。

実測日は 2026-08-08。cache-control 値は年単位で安定します。ビルド ID は毎日動きます。