Frontend Development 16 min read

Building a Frontend Performance Monitoring SDK: Theory, Metrics, and Implementation

This article explains the importance of frontend performance monitoring, outlines core metrics such as FCP, LCP, FP, CLS, and demonstrates how to implement a comprehensive SDK using PerformanceObserver, custom configuration, and wrappers for fetch and XMLHttpRequest to capture resource and network data for batch reporting.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Building a Frontend Performance Monitoring SDK: Theory, Metrics, and Implementation

In modern frontend development, performance monitoring has become essential for detecting issues, analyzing user behavior, and supporting business decisions. The author introduces a series of articles that combine theory with practice to build a complete frontend monitoring platform.

Core Metrics : The article lists key performance indicators—resource loading details, FCP, LCP, FP, LOAD, FMP, CLS, FID, and TTI—explaining their meanings and calculation methods.

Data Collection Methods : Two approaches are discussed: the modern PerformanceObserver API, which captures performance entries in real time, and the legacy PerformanceTiming API, which is now deprecated. The author recommends using PerformanceObserver exclusively.

Global Configuration :

export const config = {
  url: 'http://127.0.0.1:3000/api/data', // reporting endpoint
  projectName: 'monitor',
  appId: '123456',
  userId: '123456',
  isAjax: false,
  batchSize: 5,
  containerElements: ['html', 'body', '#app', '#root'],
  skeletonElements: [],
  reportBefore: () => {},
  reportAfter: () => {},
  reportSuccess: () => {},
  reportFail: () => {}
};

Resource Data Type Definition :

type commonType = {
  type: string;
  subType: string;
  timestamp: number;
};

export type PerformanceResourceType = commonType & {
  name: string;
  dns: number;
  duration: number;
  protocol: string;
  redirect: number;
  resourceSize: number;
  responseBodySize: number;
  responseHeaderSize: number;
  sourceType: string;
  startTime: number;
  subType: string;
  tcp: number;
  transferSize: number;
  ttfb: number;
  pageUrl: string;
};

Resource Observer Implementation captures each resource entry, filters out SDK‑generated requests, constructs a PerformanceResourceType object, batches the data, and reports it via lazyReportBatch :

export function observerEvent() {
  const config = getConfig();
  const url = config.url;
  const host = new URL(url).host;
  const entryHandler = (list) => {
    const dataList = [];
    for (const entry of list.getEntries()) {
      const resourceEntry = entry;
      if (resourceEntry.name.includes(host)) continue;
      const data = {
        type: TraceTypeEnum.performance,
        subType: resourceEntry.entryType,
        name: resourceEntry.name,
        sourceType: resourceEntry.initiatorType,
        duration: resourceEntry.duration,
        dns: resourceEntry.domainLookupEnd - resourceEntry.domainLookupStart,
        tcp: resourceEntry.connectEnd - resourceEntry.connectStart,
        redirect: resourceEntry.redirectEnd - resourceEntry.redirectStart,
        ttfb: resourceEntry.responseStart,
        protocol: resourceEntry.nextHopProtocol,
        responseBodySize: resourceEntry.encodedBodySize,
        responseHeaderSize: resourceEntry.transferSize - resourceEntry.encodedBodySize,
        transferSize: resourceEntry.transferSize,
        resourceSize: resourceEntry.decodedBodySize,
        startTime: resourceEntry.startTime,
        pageUrl: window.location.href,
        timestamp: Date.now()
      };
      dataList.push(data);
    }
    lazyReportBatch({
      type: TraceTypeEnum.performance,
      subType: TraceSubTypeEnum.resource,
      resourceList: dataList,
      timestamp: Date.now()
    });
  };
  const observer = new PerformanceObserver(entryHandler);
  observer.observe({ type: 'resource', buffered: true });
}

Page Load Timing records the time between navigation start and the load event, reporting the duration as a performance entry.

export default function observePageLoadTime() {
  const startTimestamp = performance.now();
  window.addEventListener('load', () => {
    const loadTimestamp = performance.now();
    const loadTime = loadTimestamp - startTimestamp;
    lazyReportBatch({
      type: TraceTypeEnum.performance,
      subType: TraceSubTypeEnum.load,
      entryType: 'load',
      pageUrl: window.location.href,
      startTime: startTimestamp,
      duration: loadTime,
      timestamp: Date.now()
    });
  });
}

Paint Metrics (FCP, LCP, FP) are captured with separate observers that listen for paint and largest-contentful-paint entries, convert them to JSON, add metadata, and batch‑report them.

Network Request Monitoring overrides the native fetch function and the XMLHttpRequest prototype to record request URL, method, timing, status, and parameters, then reports the data using the same batch mechanism.

// fetch wrapper example
const originalFetch = window.fetch;
window.fetch = function newFetch(url, config) {
  const startTime = Date.now();
  const report = { type: TraceTypeEnum.performance, subType: TraceSubTypeEnum.fetch, url: typeof url === 'string' ? url : url.href, method: config?.method || 'GET', startTime, pageUrl: location.href, timestamp: Date.now() };
  return originalFetch(url, config)
    .then(res => { report.status = res.status; return res; })
    .catch(err => { report.status = err.status; throw err; })
    .finally(() => {
      report.endTime = Date.now();
      report.duration = report.endTime - startTime;
      report.success = report.status >= 200 && report.status < 300;
      lazyReportBatch(report);
    });
};

The article concludes by noting that the performance monitoring portion is complete and invites readers to provide feedback or report errors, with a promise to cover error monitoring in the next installment.

SDKJavaScriptPerformance MonitoringPerformanceObserver
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.