<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Spa on Tarragon</title><link>https://tarrragon.github.io/blog/tags/spa/</link><description>Recent content in Spa on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Fri, 19 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/spa/index.xml" rel="self" type="application/rss+xml"/><item><title>JS/TS 平台適配</title><link>https://tarrragon.github.io/blog/monitoring/05-platform-adaptation/js-ts-platform/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/monitoring/05-platform-adaptation/js-ts-platform/</guid><description>&lt;p>瀏覽器環境中的監控 SDK 面臨三個平台特有的限制：跨域請求被 CORS 攔截、Service Worker 可以攔截和修改請求、SPA 的路由變換不觸發頁面載入事件。每個限制需要 SDK 在設計層面做適配。&lt;/p>
&lt;h2 id="cors-限制">CORS 限制&lt;/h2>
&lt;p>瀏覽器的同源政策限制網頁向不同 origin 發送請求。SDK 的 HTTP POST 送到 collector endpoint 時，如果 collector 和網頁不在同一個 origin（protocol + domain + port 都相同），瀏覽器會先發送 preflight OPTIONS 請求確認 server 允許跨域存取。&lt;/p>
&lt;p>SDK 端的適配：&lt;/p>
&lt;p>使用 &lt;code>navigator.sendBeacon(url, data)&lt;/code> 代替 &lt;code>fetch&lt;/code> / &lt;code>XMLHttpRequest&lt;/code>。sendBeacon 不受 CORS 限制（瀏覽器對 beacon 請求不做 preflight），且在頁面 unload 時仍能可靠送出 — 適合 close flush 場景。&lt;/p>
&lt;p>sendBeacon 的限制：payload 大小有上限（通常 64KB），不能自訂 Content-Type header（固定為 &lt;code>text/plain&lt;/code> 或 &lt;code>application/x-www-form-urlencoded&lt;/code>），沒有回應 — 送出後無法知道 server 是否收到。&lt;/p>
&lt;p>如果需要 fetch（例如需要讀取回應或送出大 payload），collector 端需要設定 CORS header：&lt;code>Access-Control-Allow-Origin&lt;/code>、&lt;code>Access-Control-Allow-Methods: POST&lt;/code>、&lt;code>Access-Control-Allow-Headers: Content-Type&lt;/code>。&lt;/p>
&lt;h2 id="service-worker-攔截">Service Worker 攔截&lt;/h2>
&lt;p>Service Worker 可以攔截頁面發出的所有 HTTP 請求（包括 SDK 的 POST 請求到 collector）。如果應用程式的 Service Worker 有 cache 策略（cache-first、network-first），SDK 的監控請求可能被快取而非送到 collector。&lt;/p>
&lt;p>SDK 端的適配：&lt;/p>
&lt;p>在 fetch 請求中加 &lt;code>cache: 'no-store'&lt;/code> 防止 Service Worker 快取監控請求。或在請求 URL 加唯一的 query parameter（&lt;code>?_t=timestamp&lt;/code>）讓每次請求的 URL 都不同，繞過 cache 比對。&lt;/p>
&lt;p>如果 SDK 本身提供 Service Worker 模組（在 Service Worker 內攔截 error），需要注意 Service Worker 的生命週期和頁面不同 — Service Worker 可能在頁面關閉後仍在執行，也可能在空閒時被瀏覽器終止。&lt;/p>
&lt;h2 id="spa-路由變換偵測">SPA 路由變換偵測&lt;/h2>
&lt;p>Single Page Application 的路由變換（React Router、Vue Router、Angular Router）不觸發頁面重新載入。從監控角度看，使用者在不同「頁面」之間切換，但 &lt;code>window.onload&lt;/code> 只在首次載入時觸發一次。&lt;/p>
&lt;p>SDK 需要偵測 SPA 路由變換來記錄 &lt;code>lifecycle.view.change&lt;/code> 事件。偵測方式：&lt;/p>
&lt;p>&lt;code>History API&lt;/code> 攔截：monkey-patch &lt;code>history.pushState&lt;/code> 和 &lt;code>history.replaceState&lt;/code>，在呼叫前後記錄路由變換。同時監聽 &lt;code>popstate&lt;/code> 事件處理瀏覽器的上一頁/下一頁。&lt;/p>
&lt;p>&lt;code>MutationObserver&lt;/code>：監聽 DOM 變化偵測頁面內容更新。但 MutationObserver 觸發頻率高，需要 debounce 並搭配 URL 變化檢查，避免把 DOM 微調誤判為路由變換。&lt;/p>
&lt;p>框架特定的 hook：如果 SDK 提供框架整合套件（React / Vue / Angular plugin），可以用框架的 router 事件（&lt;code>useNavigate&lt;/code> hook、&lt;code>router.afterEach&lt;/code> guard）直接取得路由變換資訊，比 monkey-patch History API 更可靠。&lt;/p>
&lt;p>JS/TS 的平台限制理解後，其他平台各有各的挑戰 — &lt;a href="https://tarrragon.github.io/blog/monitoring/05-platform-adaptation/flutter-platform/" data-link-title="Flutter 平台適配" data-link-desc="Isolate 安全、Platform channel 攔截、app lifecycle 事件 — Flutter SDK 的平台特殊考量">Flutter 平台適配&lt;/a>處理 isolate 和 platform channel 的問題。所有平台共同面對的 &lt;a href="https://tarrragon.github.io/blog/monitoring/05-platform-adaptation/cross-platform-timestamp/" data-link-title="跨平台 timestamp 一致性" data-link-desc="時區、精度、clock drift — 不同平台產生的 timestamp 在 collector 端需要能正確比對和排序">timestamp 一致性&lt;/a>問題（時區、精度、clock drift）在獨立章節中展開。SDK 的跨平台公開 API 設計見&lt;a href="https://tarrragon.github.io/blog/monitoring/03-sdk-design/public-api/" data-link-title="SDK 公開 API 設計" data-link-desc="init / event / error / metric / flush / close 六個方法構成 SDK 的完整生命週期 — 跨平台共用相同 API 介面">模組三 SDK 公開 API&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>瀏覽器環境中的監控 SDK 面臨三個平台特有的限制：跨域請求被 CORS 攔截、Service Worker 可以攔截和修改請求、SPA 的路由變換不觸發頁面載入事件。每個限制需要 SDK 在設計層面做適配。</p>
<h2 id="cors-限制">CORS 限制</h2>
<p>瀏覽器的同源政策限制網頁向不同 origin 發送請求。SDK 的 HTTP POST 送到 collector endpoint 時，如果 collector 和網頁不在同一個 origin（protocol + domain + port 都相同），瀏覽器會先發送 preflight OPTIONS 請求確認 server 允許跨域存取。</p>
<p>SDK 端的適配：</p>
<p>使用 <code>navigator.sendBeacon(url, data)</code> 代替 <code>fetch</code> / <code>XMLHttpRequest</code>。sendBeacon 不受 CORS 限制（瀏覽器對 beacon 請求不做 preflight），且在頁面 unload 時仍能可靠送出 — 適合 close flush 場景。</p>
<p>sendBeacon 的限制：payload 大小有上限（通常 64KB），不能自訂 Content-Type header（固定為 <code>text/plain</code> 或 <code>application/x-www-form-urlencoded</code>），沒有回應 — 送出後無法知道 server 是否收到。</p>
<p>如果需要 fetch（例如需要讀取回應或送出大 payload），collector 端需要設定 CORS header：<code>Access-Control-Allow-Origin</code>、<code>Access-Control-Allow-Methods: POST</code>、<code>Access-Control-Allow-Headers: Content-Type</code>。</p>
<h2 id="service-worker-攔截">Service Worker 攔截</h2>
<p>Service Worker 可以攔截頁面發出的所有 HTTP 請求（包括 SDK 的 POST 請求到 collector）。如果應用程式的 Service Worker 有 cache 策略（cache-first、network-first），SDK 的監控請求可能被快取而非送到 collector。</p>
<p>SDK 端的適配：</p>
<p>在 fetch 請求中加 <code>cache: 'no-store'</code> 防止 Service Worker 快取監控請求。或在請求 URL 加唯一的 query parameter（<code>?_t=timestamp</code>）讓每次請求的 URL 都不同，繞過 cache 比對。</p>
<p>如果 SDK 本身提供 Service Worker 模組（在 Service Worker 內攔截 error），需要注意 Service Worker 的生命週期和頁面不同 — Service Worker 可能在頁面關閉後仍在執行，也可能在空閒時被瀏覽器終止。</p>
<h2 id="spa-路由變換偵測">SPA 路由變換偵測</h2>
<p>Single Page Application 的路由變換（React Router、Vue Router、Angular Router）不觸發頁面重新載入。從監控角度看，使用者在不同「頁面」之間切換，但 <code>window.onload</code> 只在首次載入時觸發一次。</p>
<p>SDK 需要偵測 SPA 路由變換來記錄 <code>lifecycle.view.change</code> 事件。偵測方式：</p>
<p><code>History API</code> 攔截：monkey-patch <code>history.pushState</code> 和 <code>history.replaceState</code>，在呼叫前後記錄路由變換。同時監聽 <code>popstate</code> 事件處理瀏覽器的上一頁/下一頁。</p>
<p><code>MutationObserver</code>：監聽 DOM 變化偵測頁面內容更新。但 MutationObserver 觸發頻率高，需要 debounce 並搭配 URL 變化檢查，避免把 DOM 微調誤判為路由變換。</p>
<p>框架特定的 hook：如果 SDK 提供框架整合套件（React / Vue / Angular plugin），可以用框架的 router 事件（<code>useNavigate</code> hook、<code>router.afterEach</code> guard）直接取得路由變換資訊，比 monkey-patch History API 更可靠。</p>
<p>JS/TS 的平台限制理解後，其他平台各有各的挑戰 — <a href="/blog/monitoring/05-platform-adaptation/flutter-platform/" data-link-title="Flutter 平台適配" data-link-desc="Isolate 安全、Platform channel 攔截、app lifecycle 事件 — Flutter SDK 的平台特殊考量">Flutter 平台適配</a>處理 isolate 和 platform channel 的問題。所有平台共同面對的 <a href="/blog/monitoring/05-platform-adaptation/cross-platform-timestamp/" data-link-title="跨平台 timestamp 一致性" data-link-desc="時區、精度、clock drift — 不同平台產生的 timestamp 在 collector 端需要能正確比對和排序">timestamp 一致性</a>問題（時區、精度、clock drift）在獨立章節中展開。SDK 的跨平台公開 API 設計見<a href="/blog/monitoring/03-sdk-design/public-api/" data-link-title="SDK 公開 API 設計" data-link-desc="init / event / error / metric / flush / close 六個方法構成 SDK 的完整生命週期 — 跨平台共用相同 API 介面">模組三 SDK 公開 API</a>。</p>
]]></content:encoded></item></channel></rss>