Forest Book
サードパーティ製スクリプトはストアフロント全体を巻き込む ― fetch差し替えとタイマー暴走の切り分け

サードパーティ製スクリプトはストアフロント全体を巻き込む ― fetch差し替えとタイマー暴走の切り分け

2026年8月2日

Shopifyストアに入れたアプリのスクリプトを境に、カート操作が固まる・決済ボタンが消える・アプリと無関係なはずの箇所まで壊れる。こうした「原因の見えない不具合」は、たいていサードパーティ製のフロントエンドバンドルが、ストアフロント全体の挙動を書き換えていることに起因する。この記事は、EC事業者と、他社製アプリのトラブルに向き合う技術者に向けて、当社が実際の障害対応で使っている切り分けの考え方を整理したものだ。結論を先に言えば、フロントに読み込まれる1本のスクリプトは、そのアプリの機能範囲だけでなくサイト全体の通信とDOMに影響し得る。だから「どのアプリが原因か」ではなく「どの経路で全体に波及しているか」で見る必要がある。

1本のスクリプトがなぜサイト全体を壊すのか

アプリのフロントエンドバンドルは、多くの場合「自分の機能に必要な処理」だけをしているわけではない。ページ読み込み時に、通信をフックしたり、スタイルを注入したり、DOMを監視したりと、ブラウザのグローバルな仕組みに手を入れることが珍しくない。ここが全体波及の入口になる。

当社が実際に調査したケースでは、決済ボタンの出し分けを目的としたバンドルが、次の三つの経路でサイト全体を巻き込んでいた。いずれも「そのアプリの機能」とは別の、副作用としての破壊だった。

タイマーの暴走 ― setTimeoutとsetIntervalの取り違え

もっとも劇的に効くのが、初期化ループの取り違えだ。目的の状態が整うまで初期化関数をリトライする、という処理はよくある。問題は、そのリトライを setTimeout(1回だけ後で実行)ではなく setInterval(一定間隔で繰り返し実行)で書いてしまうケースだ。

初期化関数の中でさらにリトライ用のタイマーやDOM監視(MutationObserver)を張ると、条件が満たされない間、間隔ごとに新しいタイマーと監視が「増殖」する。どれもクリアされないため、数秒でタブが固まる。さらに悪いのは、いったん条件が満たされて本処理に入っても、増殖済みのタイマーが止まらず、本処理(たとえば通信フックの登録)が延々と積み上がることだ。この状態でカート操作をすると、積み上がったフックの数だけ同じAPIへリクエストが飛び、ページが固まる、レート制限で弾かれる、ボタンが点滅する、といった症状になる。

切り分けは単純で、増殖しているものを数えればいい。開発者ツールで同一IDの要素数や、特定要素の重複数を確認し、2以上あれば多重実行が起きている。ネットワークタブでカート操作1回あたりのリクエスト本数を見れば、フックの積み上がりも見える。

通信のグローバル差し替え ― fetch/XHRを書き換えるライブラリ

次に波及が広いのが、fetchXMLHttpRequest をグローバルに差し替えるタイプの処理だ。通信を横取りして加工するために、window.fetch そのものを別実装に置き換えるライブラリがある。これはアプリが有効かどうかに関係なく、バンドルが読み込まれた時点でサイト全体の通信に適用される。

副作用は多岐にわたる。ストリーミング送信やバイナリのPOSTが壊れる、modecredentials などの既定値が本来とわずかにズレる、withCredentials の初期値が書き換わる、といったことが起きる。厄介なのは、これがそのアプリの機能とは無関係な箇所――テーマのスクリプト、計測タグ、他のアプリ、決済系のバンドル――まで巻き込む点だ。「入れたアプリと全然関係ないところが壊れた」という報告は、この経路を疑うのが早い。

確認方法もはっきりしている。コンソールで window.fetch.toString() を見て、ネイティブ実装でなければ通信フックが当たっている。

スタイル注入とDOM構造への決め打ち

三つめは表示の破壊だ。初期化のたびに同じ <style> を追加していく実装だと、「まず全部隠す」というルールが何重にも積まれ、表示条件を満たしても要素が二度と出てこなくなる。逆に表示側で width:100%; height:100% のような強い指定を当てていると、テーマによってはボタンが異常に大きく崩れる。

さらに危ういのが、DOM構造を決め打ちで触る処理だ。「特定要素の2階層上の高さを0にする」といった実装は、そのテーマでは狙った要素でも、別のテーマでは購入ブロック全体やカートドロワーのフッターだったりする。結果、無関係なUIごと潰れる。テーマをまたいで配布されるアプリが、親要素の構造を前提に書かれていると、テーマ差でこうした事故が起きる。

原因がスクリプト側か環境側かを先に確定させる

障害対応で最初にやるべきは、犯人捜しではなく「原因がこのスクリプトにあるのか、環境側にあるのか」の確定だ。もっとも早いのは、該当アプリのApp Embedやスクリプトを一度オフにして、他の不具合が消えるかを見ることだ。消えればスクリプト側、残れば環境側と切り分けられる。

原因がベンダー配布のバンドルにある場合、こちらで手を入れて直すより、上に挙げた挙動(特にタイマーの取り違え)を具体的な再現条件つきでベンダーに報告し、ビルドを直してもらうのが本筋だ。当社では、暫定運用が必要なときに限って読み込みタイミングを前倒しするなどの回避策を取るが、それはあくまで一時しのぎと位置づけている。そもそも決済ボタンの出し分けのような要件は、フロントのCSSハックではなく、チェックアウト側の拡張(Plusであれば決済カスタマイズの仕組み)で実装し直すほうが、テーマ差にも強く安全だ。

まとめ

ストアフロントに読み込まれる1本のスクリプトは、そのアプリの機能範囲を超えて、サイト全体の通信・スタイル・DOMを書き換え得る。だから不具合が出たときは、「どのアプリが悪いか」の前に「どの経路で全体に波及しているか」を見る。タイマーの増殖、通信のグローバル差し替え、スタイルとDOM構造への決め打ち――この三つを押さえておけば、原因の見えない不具合の大半は切り分けられる。当社では、他社製アプリを含むストアの障害対応でも、まずアプリを一度止めて切り分け、再現条件を添えてベンダーに返す、という手順を徹底している。