innerHTMLとinsertAdjacentHTMLは、20年以上にわたってDOMへHTMLを差し込む主要な手段でした。しかし、挿入位置を文字列で指定する、サニタイズをしない、ストリーミングに向かないという3つの弱点を抱えています。AIが生成したHTMLやCMSのプレビューを扱う場面が増えた今、このAPIは限界に近づいています。
Chrome 157(2026年11月3日 stable予定)では、HTMLの挿入とストリーミングを整理した新しいメソッド群が追加されます。挿入位置ごとにメソッドを分け、安全版と非安全版を明示し、WritableStreamで少しずつ流し込めるようにするものです。この記事では、APIの形、移行方法、セキュリティ上の注意、そして今使うべきかどうかを整理します。
30秒でわかる結論
- Chrome 157で
appendHTMLやbeforeHTMLなど、挿入位置ごとのメソッドが追加される。stableは2026年11月3日予定。 - 文字列の挿入位置指定(
beforeendなど)がなくなり、メソッド名で意図が読めるようになる。 - 安全版は既定のサニタイザーを通し、非安全版は
runScriptsなどのオプションを明示できる。 streamAppendHTML系はWritableStreamを返し、HTMLを分割して流し込める。- 現時点ではChrome系のみ。caniuseでは既定無効として記録されているため、実行前の機能検出が必須。
何が起きたのか:Chrome 157の新API
Chrome 157のリリースノートには「Renewed HTML insertion and streaming methods」として、次の4点が記載されています。位置指定メソッドがHTML文字列を受け取ること、ストリーミング用のメソッドがWritableStreamを返すこと、setHTMLUnsafeにrunScriptsオプションが加わること、Trusted Typesがパーサーオプションを扱えるようになることです。
背景にはWHATWGの議論があります。HTMLをDOMへ入れる方法は、setHTML、setHTMLUnsafe、innerHTML、outerHTML、createContextualFragment、insertAdjacentHTMLと乱立し、それぞれサニタイズの有無やスクリプト実行の挙動が異なっていました。WICGのexplainerでは、これを「同期かストリーミングか」「挿入位置はどこか」「安全か非安全か」の組み合わせとして整理し、最終的に24個のメソッドに落とし込んでいます。
このAPIはChrome 157のリリースノートに記載されていますが、執筆時点(2026年10月23日)のChrome 156 stableには入っていません。caniuseでも既定無効と記録されています。検証するときは
'appendHTML' in Element.prototypeで存在を確認してください。
何が変わるのか:旧APIとの対応
既存APIは、新しいAPIで次のように表現できます。表の左側がこれまでの書き方、右側が新しい書き方です。
これまで | Chrome 157以降 | サニタイズ |
|---|---|---|
|
| なし |
|
| 既定のサニタイザー |
|
| なし |
|
| なし |
|
| なし |
|
| なし |
|
| なし |
注目すべきは、innerHTMLがsetHTMLUnsafeと等価であり、安全側のsetHTMLとは別物だという点です。これまでは「とりあえずinnerHTML」が既定でしたが、新しいAPIでは呼び出し箇所を見ただけで安全性が判断できます。コードレビューで「このHTMLはどこから来たのか」を追う負担が減ります。
使い方・最小実装
基本は、挿入したい位置に対応するメソッドを呼ぶだけです。安全版には既定のサニタイザーが適用されます。
const list = document.querySelector('#comments');
// 安全版: 既定のサニタイザーを通す
list.appendHTML('<p>読み込み済み</p>', { sanitizer: 'default' });
// 非安全版: 自前で検証済みのHTMLだけを入れる
list.appendHTMLUnsafe(trustedMarkup);安全版はjavascript:URLを含む属性を除去します。非安全版でも、既定のサニタイザーを使う場合はjavascript:URLが除去される挙動に整理されました。Chrome 157ではSanitizerConfigにjavascriptURLsオプションが追加され、この挙動を開発者が選べるようになります。
機能検出は次の1行で足ります。未対応ブラウザでは従来のAPIにフォールバックします。
const supportsHtmlSetters = 'appendHTML' in Element.prototype;
function appendHtml(el, html) {
if (supportsHtmlSetters) {
el.appendHTML(html, { sanitizer: 'default' });
} else {
el.insertAdjacentHTML('beforeend', html);
}
}フォールバック側はサニタイズされないため、信頼できないHTMLを渡す場合は別途DOMPurifyなどで処理してください。ここを省略すると、新しいAPIを入れた意味がなくなります。
ストリーミングで流し込む
ストリーミング版はWritableStreamを返します。サーバーから届いたHTMLを分割して書き込み、閉じた時点でDOMへ反映されます。大量のコメントや検索結果を、最初のチャンクから順に表示したい場面に向きます。
const feed = document.querySelector('#feed');
const response = await fetch('/api/feed');
const writer = feed.streamAppendHTML({ sanitizer: 'default' }).getWriter();
const decoder = new TextDecoderStream();
response.body.pipeTo(decoder.writable);
for await (const chunk of decoder.readable) {
await writer.write(chunk);
}
await writer.close();このコードは安全版です。サニタイズしながらパースするため、途中で危険なタグが混ざっても除去されます。非安全版のstreamAppendHTMLUnsafeは、スクリプト実行を伴うユースケース向けですが、信頼境界を自分で設計する必要があります。
注意点として、ストリーミング中のスクリプト実行は通常のパーサーに近い挙動になります。クラシックスクリプトは後続のパースをブロックし、deferやモジュールはストリームが閉じた後に実行されます。同期的なcreateContextualFragmentとはタイミングが異なるため、既存の初期化処理をそのまま持ち込むと順序が変わります。
スクリプト実行とサニタイザー
新しいAPIでは、スクリプトを実行するかどうかが明示的なオプションになりました。setHTMLUnsafeにrunScripts: trueを渡すと、createContextualFragmentに近い形でスクリプトが実行されます。既定はfalseです。
container.setHTMLUnsafe(markup, {
runScripts: true,
sanitizer: 'default'
});runScriptsは安全版のsetHTMLでは使えません。安全版は常にスクリプトを除去します。これは仕様上の意図的な非対称で、「安全とスクリプト実行は両立しない」というメッセージです。
Trusted Typesとの統合も変わります。これまではHTML文字列をポリシーで変換してからパーサーへ渡していましたが、ストリーミングでは文字列全体が揃うまで待てません。そこでcreateHTMLParserOptionsというメソッドで、サニタイザーやrunScriptsを含むオプション自体を承認する形になります。
既存のDOMPurifyを外せる?→ すぐには外さないでください。新しいAPIのサニタイザーはブラウザ実装であり、設定の互換性や許可リストはプロジェクトごとに異なります。まずは二重に適用し、差分が出ないことを確認してから段階的に移行するのが安全です。
移行手順
移行は、いきなり全置換するのではなく、危険度の高い箇所から進めます。
innerHTMLとinsertAdjacentHTMLの呼び出し箇所を洗い出す。テンプレートリテラルで組み立てている箇所は特に注意する。- ユーザー入力やAPIレスポンスを入れている箇所を、
setHTMLまたはappendHTMLの安全版へ置き換える。 - 自前でサニタイズ済みのHTMLだけを入れている箇所は、
...Unsafeのまま据え置く。無理に安全版へ変えると表示が変わる。 - 機能検出のフォールバックを共通関数にまとめ、呼び出し側から分岐を消す。
- 長いリストや無限スクロールは、ストリーミング版へ置き換えられるか検証する。
置き換えの単位は「1つのコンポーネント」ではなく「1つのデータ経路」にすると安全です。同じAPIレスポンスを複数のコンポーネントが描画している場合、片方だけを新しいAPIにすると、サニタイズの有無が場所によって変わり、脆弱性の穴になります。
実務への影響
影響が大きいのは、CMSのプレビューとAI生成HTMLの表示です。どちらも「外部から来たHTMLをそのままDOMへ入れる」処理で、これまではサニタイズを呼び出し側の善意に任せていました。新しいAPIでは、安全版を選ぶことが自然な実装になります。
ストリーミングは、検索結果やコメント欄のような「上から順に表示したいが、全体が揃うまで待ちたくない」画面と相性が良いです。ReactやVueのサーバーサイドストリーミングと競合するものではなく、フレームワークの外側でDOMを直接扱う場面(埋め込みウィジェット、レガシー画面、マイクロフロントエンド)で効きます。
一方で、フレームワークの仮想DOMと直接DOM操作を混ぜると、状態の不整合が起きます。Reactの管理下にある要素に対してappendHTMLを呼ぶと、次の再レンダリングで消えるか、逆に残骸が残ります。移行対象は、フレームワークが管理していない領域に限定してください。
注意点
- 対応はChrome系のみ。SafariとFirefoxは未対応で、polyfillもストリーミングをバッファリングするなど制約があります。
- 既定無効の可能性。caniuseは執筆時点で「Disabled by default」と記録しています。フィーチャーフラグやオリジントライアルの状態を確認してください。
- 安全版でも過信しない。サニタイザーの許可リストはユースケースによって変わります。リンクの
targetやrel、画像のURLスキームなど、アプリ固有のポリシーは別途検証が必要です。 - スクリプト実行の順序。ストリーミングでは
deferやモジュールの実行タイミングが同期版と異なります。初期化処理の前提を確認してください。 - Trusted Typesの移行。
createHTMLParserOptionsはポリシー側の対応が必要で、既存のcreateHTMLだけではストリーミングを承認できません。
対応状況と使う判断
ブラウザ | 対応 | 備考 |
|---|---|---|
Chrome / Edge 157以降 | 予定 | 2026年11月3日 stable予定。執筆時点はBeta |
Safari | 未対応 |
|
Firefox | 未対応 | Sanitizer APIの一部のみ |
Baseline | 未到達 | 機能検出とフォールバックが必須 |
自分なら使うか:条件付きで使う。 新規の管理画面や埋め込みウィジェットで、外部HTMLを表示する箇所から試験導入します。条件は3つです。機能検出の共通関数を用意すること、既存のサニタイズを二重にかけて差分を確認すること、フレームワーク管理下のDOMには触らないこと。この3つを守れるなら、innerHTMLより意図が明確でレビューしやすいコードになります。逆に、すでに安定して動いているレガシー画面をわざわざ書き換える価値は、まだないと判断します。
まとめ
- Chrome 157でHTML挿入APIが整理され、挿入位置ごとのメソッドとストリーミング版が追加される。
innerHTMLはsetHTMLUnsafe相当。安全版のsetHTMLとは明確に分かれる。runScriptsは非安全版のみ。安全版は常にスクリプトを除去する。- ストリーミングは
WritableStreamを返し、パースしながらDOMへ反映する。 - Chrome系のみの対応のため、機能検出とフォールバックを前提に段階導入する。






