Optimizing the Size and Performance of a Frontend Monitoring SDK
This article explains how ByteDance's frontend monitoring SDK was systematically reduced in bundle size and runtime overhead through micro‑optimizations such as avoiding long class names, using functional patterns, minimizing polyfills, splitting files, leveraging Puppeteer‑based benchmarks, and applying requestIdleCallback scheduling to improve user experience on both web and mobile platforms.
Background
ByteDance's various services serve billions of users, so any performance issue in its frontend monitoring SDK would affect a massive user base. Therefore, the SDK must be designed to meet extremely high performance standards from the start.
As business requirements grow, new features and custom metric rules increase the SDK's complexity, leading to larger bundle sizes and potential performance degradation. The article discusses how to continuously track and optimize the SDK's size and performance amid evolving requirements.
SDK Bundle Size Optimization
Bundle size is often the easiest area to achieve performance gains because the SDK loads as the first script on a page, affecting download and parsing time. Optimizations are approached from macro and micro perspectives.
Micro‑optimizations include removing redundant code, shortening class and method names, and avoiding patterns that hinder compression. For example, a class with a long name expands dramatically after TypeScript compilation and minification, whereas an equivalent functional implementation reduces the size by over 50%.
class ClassWithLongName {
methodWithALongLongName() {}
}After TypeScript compilation and minification, the class version remains large, while the functional version compresses much better.
function functionWithLongName() {
return function MethodWithALongLongName() {}
}Other micro‑optimizations include using arrays instead of objects for internal function parameters, avoiding unnecessary nullable checks (?., ??, ??=), and limiting the use of newer syntax that requires polyfills.
function report(event, [optionA, optionB, optionC, optionD]) {}Reducing repeated constant strings by extracting them into shared variables also helps shrink the bundle.
let ADD_EVENT_LISTENER = 'addEventListener';
let LOAD = 'load';
a[ADD_EVENT_LISTENER](LOAD, cb);While gzip compression mitigates repeated strings on the web, extracting constants can still save 10‑15% of bundle size for SDKs embedded in mobile apps.
Performance Measurement Tools
The SDK's performance impact is measured by focusing on three areas: initialization listeners, event reporting requests, and memory usage of cached data.
Performance Measurement Process
Using a benchmark tool (e.g., benny ) helps identify the execution time of each SDK function and detect long‑tasks. Two test modules are defined: automatic monitoring that runs on page load, and client‑side API calls invoked by developers.
const { suite, add, cycle, complete } = require('benny');
suite('collectors setup',
add('route', () => route(context)),
add('exception', () => exception(context)),
// ... other collectors
cycle(),
complete()
);Because the SDK relies on browser APIs, benchmarks are executed in a headless Chrome environment using Puppeteer, which captures both timing results and profiling data.
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.exposeFunction('pushResult', result => benchmark.results.push(result));
// run benchmark script in the page
await page.addScriptTag({ content: file.toString() });Results show that performance observers can add up to 21 ms of overhead, and combined listeners for metrics like FCP, LCP, and CLS exceed 10 ms, prompting further optimization.
Analyzing Performance with Perfsee Lab
Perfsee Lab provides real‑world performance analysis by running pages in a headless browser and comparing a baseline (no SDK) with an experimental page (SDK injected). The comparison reveals that the SDK adds roughly 70 ms to FCP, 90 ms to LCP, and 200 ms to load on mobile devices.
Flame‑graph profiling identifies specific listeners (e.g., onBFCacheRestore ) that consume significant main‑thread time.
const onBFCacheRestore = cb => {
addEventListener('pageshow', e => { if (e.persisted) cb(e) }, true);
};Since these listeners are not high‑priority, they can be deferred or shared across metrics.
Problem Analysis and Optimization
Key optimizations applied:
Split monitoring tasks and schedule non‑critical work with requestIdleCallback based on priority.
Share a single listener for multiple metrics that observe the same event.
Batch network requests and use navigator.sendBeacon for reliable data transmission during page unload.
After these changes, the SDK's impact on FCP, LCP, and load times was reduced to minimal levels, meeting high performance standards.
Learn More
ByteDance's frontend monitoring solution is now available on Volcano Engine, offering a free 30‑day trial for developers.
ByteDance Terminal Technology
Official account of ByteDance Terminal Technology, sharing technical insights and team updates.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.