← シリーズに戻る

cache-control 設定と版固定: 5製品で戦略が割れる

Part 4 / 6 JavaScript SDK 設計: 実在5製品のバンドル分解

postMessage の使い方と契約設計: 3世代を比較 でプロトコル契約を扱いました。プロトコルを固定したら、次は「顧客にどの版を使わせるか」です。

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

実測 cache-control 行列

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

製品URLcache-control
Safie loadersafie.link/sdk/js/api/v1/latest/no-cache
Safie iframe 本体.../lib/main-5DQ7WBMB.js (ハッシュ URL)(無し = ブラウザ任せ)
YouTube loaderyoutube.com/iframe_apiprivate, max-age=0
YouTube widgetapi 本体.../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(無し = ブラウザ任せ)
Video.js /8/vjs.zencdn.net/8/video.min.js403 Forbidden

「無し」の行は Cache-Control を返しておらず、この場合ブラウザは Last-Modified からの経過時間などをもとに新鮮期間を自分で見積もるので (RFC 9111 §4.2.2 のヒューリスティック鮮度)、どれだけ持つかはブラウザ次第です。永久ではありません。

Safie の本体はハッシュ入り URL なので、それでも古いものを掴む心配は小さく済みます。

2軸を読む

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

  • 選べる: Mux Player (@3.7.0 のように書けば固定。@latest でも返る)、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)

軸1 (顧客が版を選べるか) と軸2 (どこで版を固定するか) の表に5製品を置いた図。URL に版を含めて顧客が選べるのは DOM 直挿し型の Video.js (/8.23.4/ が必須で /8/ は 403) と Mux Player (@3.7.0 と書けば固定、@latest でも返る)。URL に無いが中でハッシュ済みで顧客が選べないのは YouTube (/iframe_api、loader が 854a788e 入りの本体 URL を出す)。URL に版が無く中身が動き顧客が選べないのは Safie (/latest/) と Vimeo (/player.js)。残りのマスはなし。iframe 型は3製品とも「顧客に版を選ばせない」列に入る。URL は 2026-08-08 の実測

iframe 型の3製品は「顧客に版を選ばせない」で揃い、DOM 型の2製品は「顧客に版を選ばせる」で揃います。Mux Player は @latest でも返し、exact version を必須にするのは Video.js だけです。5製品しか見ていないので断定はできませんが、この割れ方には技術の側から説明がつくと考えています。

iframe 型の cache-control。Safie と YouTube は二段構えで、版を含まない loader URL を no-cache や max-age=0 で毎回確認させ、ビルドハッシュ入りの本体 URL を長く持たせる。Vimeo は max-age=1800 の単一ファイルで1段。DOM 直挿し型は顧客が書く URL 自体に版を書ける

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

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

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

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

顧客に loader の版を固定させると親と子の版がずれることを示した図。上段は loader の版を顧客が固定する場合 (2年前の loader を貼りっぱなし) で、親 (顧客サイトの loader) が旧プロトコル、子 (自社の iframe 本体) が新プロトコルになり、ずれて通信が壊れる。旧 loader のために新プロトコルを我慢すると機能追加が止まり、旧 loader を切り捨てると顧客が HTML を直すことになる。下段は loader の URL は固定で中身を自社が替える場合 (Safie は /latest/ を no-cache で配る) で、毎回取り直すので親も子も新プロトコルに揃う。顧客は貼り替えなくても、親と子が同時に前進する。旧・新はプロトコルの版の模式

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

YouTube は少し変わっています。loader (iframe_api) は同じく毎回取り直させますが、その loader が 854a788e のような build hash 付き URL を吐き出す構造。build hash が変わればブラウザキャッシュを跨いで新版が引かれます。二段間接なので新版は本体の hash 付き URL が変わることで届き、loader のほうは private が付いているので企業プロキシのような共有キャッシュにはそもそも置かれません。

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

DOM 直挿し型が版を顧客に選ばせる理由

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

代わりに別の問題があります。SDK バンドルの API 変更です。videojs(id, options) の options 形式が変わったり、プラグイン API が刷新されたりすると、顧客が自前で書いた JS が壊れます。SDK 側は URL に版を書ける形で配り、顧客がアップグレードするタイミングを自分で選べるようにします。どこまで書かせるかは製品で違います。

  • Mux Player は npm パッケージなので、@3.7.0 のように semver 指定が自然。jsdelivr の CDN が @3.7.0 を immutable で1年キャッシュします。@latest と書けば新版に追従します (CDN で最大12時間、ブラウザで最大7日遅れる)。固定するかどうかは顧客に任されています。
  • Video.js は vjs.zencdn.net/8.23.4/ を必須にし、/8/ を 403 で拒否します。「メジャーバージョンだけ書いておいて勝手にマイナーアップグレード」ができない。

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

新しい版を出してから顧客のブラウザに届くまでの最大の遅れを、URL ごとに横棒で並べた図。横軸は左から次の読み込み、30分、12時間、7日、顧客が URL を書き換えるまで (順序だけで等間隔ではない)。iframe 型では、Safie loader /latest/ (no-cache) と YouTube loader /iframe_api (private, max-age=0) は毎回確認するので次の読み込みで届き、Vimeo /api/player.js (public, max-age=1800) は最大30分。DOM 直挿し型では、Mux Player @latest (max-age=604800, s-maxage=43200) は CDN で最大12時間、ブラウザで最大7日。Mux Player @3.7.0 (max-age=31536000, immutable) と Video.js /8.23.4/ (ヘッダ無し、/8/ は 403) は中身が変わらず、顧客が URL を書き換えるまで届かない。Safie と YouTube は loader を毎回確認させ、新しい本体はハッシュの変わった URL で取り直させる。cache-control は 2026-08-08 の実測 (10-01 に取り直して同じ値)

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

no-cache と max-age=0 の細かい違い

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

  • no-cache: キャッシュしていいが、使う前に必ずサーバーに再検証 (If-None-Match で 304 を狙う)
  • max-age=0: 新鮮期間がゼロなので、使う前に毎回再検証が要る状態になる。YouTube は private も付けているので、CDN や企業プロキシのような共有キャッシュには置かれない

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

Safie loader の no-cache と YouTube loader の private, max-age=0 を、誰が持てて使う前に何が起きるかで比べた表の図。題は「どちらも毎回確かめるが、置ける場所と確かめ方が違う」。ブラウザのキャッシュはどちらも持てる。共有キャッシュ (CDN・企業のプロキシ) は、Safie は持てるが使う前に確かめる、YouTube は private が付いているので置かれない。使う前に、Safie は必ずサーバーに確かめる (no-cache は「保存してよいが、確認なしに使うな」)、YouTube は新鮮な期間がゼロなので毎回確かめる。変わっていなければ、Safie は ETag と Last-Modified が付いているので 304 で中身は送られない、YouTube は ETag も Last-Modified も無いので loader 全体 (gzip 572 B) をまた受け取る。意図はどちらも同じで、loader をキャッシュで固定させない。根拠は RFC 9111 (no-cache・private・検証)、ヘッダは 2026-10-01 に curl -sI で確認

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

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

iframe 型 SDK を作るDOM 直挿し型 SDK を作る
顧客が貼る URL/latest/ 固定、no-cache版を書ける (Video.js は必須)、exact 版は 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つに割れています。もう一つ、YouTube・Vimeo・Mux Player・Video.js が配信 JS に対策を入れている「二重ロード」問題を扱います。

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