Web Performance · Part 20 — Third-Party Script Performance
Chế ngự analytics, ads, embed và tag manager: đo tác động bên thứ ba, facade pattern cho embed, Partytown để đẩy sang worker, lazy iframe, async/defer, và đặt budget cho third-party.
Bạn có thể tối ưu code của mình rồi vẫn có một trang chậm — vì tài nguyên bên thứ ba: analytics, quảng cáo, chat widget, A/B testing, tag manager, embed (YouTube, social, Maps). Script được chèn vào document chính cạnh tranh trên cùng main thread; cross-origin iframe có execution context riêng nhưng vẫn tốn mạng, bộ nhớ, render và có thể gây layout shift. Phần này về chiến lược chế ngự chúng mà không mặc định phải gỡ bỏ.
“Chỉ một đoạn snippet” có thể kéo theo nhiều request và JavaScript hơn rất nhiều so với đoạn bạn nhìn thấy. Tác động khác nhau theo vendor/cấu hình, nên phải đo CPU, network và user flow của chính trang mình.
1. Đo tác động bên thứ ba trước
Đừng đoán — đo. Cần biết script nào tốn bao nhiêu trước khi quyết định.
Công cụ đo third-party:
- DevTools Performance → group theo domain, xem CPU mỗi origin
- Lighthouse/DevTools → diagnostics CPU, request và main-thread work theo origin
- WebPageTest → request map, theo domain
- LoAF (Part 16) → script third-party nào gây khung chậm (sourceURL)
- Attribution API (Part 13) → INP có phải do script bên thứ ba?
Lập bảng “ngân sách giá trị/chi phí”: mỗi third-party tốn bao nhiêu ms main thread, đổi lại giá trị gì. Nhiều script “tưởng cần” không đáng cái giá INP của nó.
2. async, defer & vị trí đặt
Quy tắc mặc định: tránh <script> bên thứ ba đồng bộ trong <head> vì nó chặn parser; chỉ chấp nhận khi yêu cầu sản phẩm đã cân trade-off và đo rõ (ví dụ một số anti-flicker snippet).
<!-- defer: tải song song, chạy SAU khi HTML parse xong, theo thứ tự -->
<script src="https://third-party.com/widget.js" defer></script>
<!-- async: tải song song, chạy NGAY khi tải xong (thứ tự bất kỳ) -->
<script src="https://analytics.com/a.js" async></script>
defer → classic external script cần DOM/thứ tự; chạy sau parse, trước DOMContentLoaded
async → script độc lập; chạy ngay khi tải xong, thứ tự không bảo đảm
module → mặc định có hành vi defer; thêm async nếu thật sự độc lập
Tải script bên thứ ba muộn hoặc theo consent/interaction thường giảm tranh chấp với LCP/INP. Nếu analytics cần ghi nhận event sớm, queue event bằng first-party code nhẹ rồi tải vendor sau; đừng mặc định toàn bộ SDK phải chạy trước LCP.
3. Facade pattern — thay embed nặng bằng ảnh “giả”
Embed như YouTube, Google Maps, chat widget kéo theo hàng trăm KB JS dù người dùng chưa chắc tương tác. Facade: hiển thị một ảnh/placeholder nhẹ trông như embed, chỉ tải embed thật khi người dùng click.
<!-- Facade cho YouTube: ảnh thumbnail + nút play; tải iframe thật khi click -->
<div class="yt-facade" data-id="VIDEO_ID">
<img src="https://i.ytimg.com/vi/VIDEO_ID/hqdefault.jpg" alt="Tiêu đề video" width="480" height="360" decoding="async">
<button type="button" aria-label="Phát video">▶</button>
</div>
document.querySelectorAll<HTMLButtonElement>('.yt-facade button').forEach((button) => {
button.addEventListener('click', () => {
const wrap = button.closest<HTMLElement>('.yt-facade');
const id = wrap?.dataset.id;
if (!wrap || !id || !/^[\w-]{11}$/.test(id)) return;
const iframe = document.createElement('iframe');
iframe.src = `https://www.youtube-nocookie.com/embed/${id}?autoplay=1`;
iframe.title = 'Video';
iframe.allow = 'autoplay; encrypted-media; picture-in-picture';
iframe.allowFullscreen = true;
iframe.referrerPolicy = 'strict-origin-when-cross-origin';
wrap.replaceChildren(iframe);
iframe.focus();
});
});
Một embed video có thể kéo hàng trăm KB và nhiều request → facade ban đầu chỉ cần markup + thumbnail.
Có sẵn: lite-youtube-embed, react-lite-youtube-embed... cho facade chuẩn.
Facade chuyển chi phí từ “mọi lần tải trang” sang “chỉ khi người dùng thật sự dùng”. Giữ aspect-ratio/kích thước container giống iframe để click không gây CLS; chỉ thêm loading="lazy" cho thumbnail khi facade thật sự ở dưới màn hình đầu.
4. Partytown — đẩy third-party sang Web Worker
Partytown chạy script được opt-in trong Web Worker, rồi proxy các API main-thread như DOM/window. Nó có thể giảm JavaScript trực tiếp trên main thread, nhưng DOM operation vẫn phải được chuyển tiếp và có overhead; INP không tự động tốt hơn với mọi vendor.
<!-- Sau khi đã cài và bootstrap Partytown, opt-in script này vào worker -->
<script type="text/partytown" src="https://analytics.com/a.js"></script>
Cơ chế: JavaScript Proxy chuyển API call từ worker về main thread.
Transport có thể dùng Atomics/SharedArrayBuffer khi cross-origin isolated,
hoặc fallback qua service worker; DOM-heavy script có thể chậm vì bridge.
Hợp với: analytics, tag manager, tracking pixel (chủ yếu gửi sự kiện, ít cần DOM realtime).
Cẩn thận: script phụ thuộc nặng vào timing DOM/API đồng bộ có thể trục trặc — test kỹ.
Partytown không phải thuốc tiên: một số script phụ thuộc DOM đồng bộ, cookie/storage của iframe hay timing tinh vi sẽ không phù hợp. Chỉ opt-in vendor đã test, kiểm tra correctness/consent trước, rồi đo lại bằng RUM.
5. Lazy iframe & các kỹ thuật khác
<!-- iframe tải lười khi gần viewport (như ảnh lazy) -->
<iframe src="https://maps.example.com/embed" loading="lazy" title="Bản đồ" width="600" height="450"></iframe>
Kỹ thuật chế ngự khác:
- loading="lazy" cho iframe dưới màn hình; không lazy iframe/ảnh là LCP ở màn hình đầu
- tải chat widget khi cuộn/sau X giây/khi tương tác (idle import)
- preconnect tới origin third-party quan trọng (Part 14) để bớt latency kết nối
- self-host chỉ khi vendor + license hỗ trợ và bạn có quy trình cập nhật/security patch
- SRI cho URL script bất biến (kèm CORS đúng); script đổi nội dung thường xuyên sẽ làm hash fail
6. Đặt budget cho bên thứ ba & quản trị
Third-party hay được thêm bởi marketing/sales mà không qua dev. Cần quy trình quản trị, không chỉ kỹ thuật:
Budget third-party (ví dụ):
- tổng JS third-party ≤ 100KB (nén)
- tổng thời gian block main thread ≤ 150ms
- mỗi tag mới phải qua review hiệu năng trước khi thêm
- đo định kỳ; gỡ tag không còn dùng (tag manager hay tích tụ "tag chết")
- ưu tiên tag manager để bật/tắt nhanh, nhưng theo dõi chính nó cũng tốn
Quy tắc văn hóa: mỗi script bên thứ ba phải có người sở hữu và lý do. “Đặt vào năm ngoái, không ai nhớ tại sao” là tín hiệu để gỡ. Đo tác động và đưa con số cho người yêu cầu thêm tag.
7. Tóm tắt
- Script third-party trong document chính cạnh tranh trên cùng main thread; iframe vẫn tốn network/render/memory — đo trước bằng DevTools, Lighthouse và field attribution khi có.
- Dùng đúng
defer/async/module, tránh script đồng bộ chặn theo mặc định; queue event nhẹ nếu phải tải SDK muộn. - Facade pattern thay embed nặng (YouTube/Maps/chat) bằng placeholder nhẹ, chỉ tải embed thật khi click — tiết kiệm hàng trăm KB.
- Partytown proxy script opt-in qua Web Worker; bridge vẫn có overhead và compatibility trade-off, nên test correctness + hiệu năng từng vendor.
- Lazy iframe, idle-load widget, preconnect, self-host có kiểm soát + SRI cho URL bất biến là các kỹ thuật bổ trợ.
- Đặt budget third-party và quản trị: mỗi tag có chủ + lý do, review trước khi thêm, gỡ tag chết.
Phần tiếp theo: Budgets, automation & culture at scale — size-limit/bundlesize, ngân sách trong CI, phân tích treemap, bisect hồi quy, scorecard và xây văn hóa hiệu năng bền vững.