SkillAgentSearch skills...

Perfume.js

Web performance library for measuring all performance vitals metrics

Install / Use

/learn @Zizzamia/Perfume.js

README

<a href="https://zizzamia.github.io/perfume/"> <img src="https://github.com/Zizzamia/perfume.js/blob/master/docs/src/assets/perfume-logo-v5-0-0.png" align="left" width="200" alt="Perfume.js logo" /> </a>

Perfume.js v9.4.0

Current version Test Coverage <img alt="No dependencies" src="https://img.shields.io/badge/dependencies-none-27ae60.svg"> Build Status NPM Downloads gzip size brotli size

<b>Page Speed</b> is a feature, and to deliver it we need to understand the many factors and fundamental limitations that are at play. If we can measure it, we can improve it.

<br /> <br /> <br /> <br />

Why Perfume.js?

Perfume is a tiny, web performance monitoring library that reports field data back to your favorite analytics tool.

  • ⏰ Supports latest Performance APIs for precise metrics
  • 🚀 Device data enrichment
  • 🔨 Cross browser tested
  • 🚿 Filters out false positive/negative results
  • 🤙 Only 5.1Kb gzip
  • 🏅 Web Vitals Score
  • 🛰 Flexible analytics tool
  • ⚡️ Waste-zero ms with requestIdleCallback strategy built-in
  • Ability to track data about user actions <br />

The latest in metrics & Real User Measurement

Perfume leverages the latest Performance APIs to collect field data that allows us to understand what real-world users are actually experiencing.

<br /> At <a href="https://www.coinbase.com/blog/performance-vitals-a-unified-scoring-system-to-guide-performance-health-and-prioritization">Coinbase</a>, we use Perfume.js to capture a high-level scoring system that is clear, trusted, and easy to understand. <br /> <br /> Summarizing the performance health of an application into a reliable and consistent score helps increase urgency and directs company attention and resources towards addressing each performance opportunity. <br />

Perfume.js vs Web Vitals

Perfume leverages the Web Vitals library to collect all the standardized performance metrics. It explores new metrics like Navigation Total Blocking Time and dimensions like Low-End Devices, Service Worker status to understand your data better.

So don't worry, Perfume.js is a superset of Web Vitals, a bit like Typescript is a superset of Javascript.

Installing

npm (https://www.npmjs.com/package/perfume.js):

npm install perfume.js --save

Importing library

You can import the generated bundle to use the whole library generated:

import { initPerfume } from 'perfume.js';

Universal Module Definition:

import { initPerfume } from 'node_modules/perfume.js/dist/perfume.umd.min.js';
<br />

Quick start

Metrics like Navigation Timing, Network Information, TTFB, FCP, FID, LCP, CLS, INP and TBT are default reported with Perfume; All results will be reported to the analyticsTracker callback, and the code below is just one way for you to organize your tracking, feel free to tweak it suit your needs.

🚀 Visit https://zizzamia.github.io/perfume/ for a live demo on how the metrics work. 🌕

import { initPerfume } from 'perfume.js';

initPerfume({
  analyticsTracker: options => {
    const {
      attribution,
      metricName,
      data,
      navigatorInformation,
      rating,
      navigationType,
    } = options;
    switch (metricName) {
      case 'navigationTiming':
        if (data && data.timeToFirstByte) {
          myAnalyticsTool.track('navigationTiming', data);
        }
        break;
      case 'networkInformation':
        if (data && data.effectiveType) {
          myAnalyticsTool.track('networkInformation', data);
        }
        break;
      case 'storageEstimate':
        myAnalyticsTool.track('storageEstimate', data);
        break;
      case 'TTFB':
        myAnalyticsTool.track('timeToFirstByte', { duration: data });
        break;
      case 'RT':
        myAnalyticsTool.track('redirectTime', { duration: data });
        break;
      case 'FCP':
        myAnalyticsTool.track('firstContentfulPaint', { duration: data });
        break;
      case 'FID':
        myAnalyticsTool.track('firstInputDelay', { duration: data });
        break;
      case 'LCP':
        myAnalyticsTool.track('largestContentfulPaint', { duration: data });
        break;
      case 'CLS':
        myAnalyticsTool.track('cumulativeLayoutShift', { value: data });
        break;
      case 'INP':
        myAnalyticsTool.track('interactionToNextPaint', { value: data });
        break;
      case 'TBT':
        myAnalyticsTool.track('totalBlockingTime', { duration: data });
        break;
      case 'elPageTitle':
        myAnalyticsTool.track('elementTimingPageTitle', { duration: data });
        break;
      case 'userJourneyStep':
        myAnalyticsTool.track('userJourneyStep', {
          duration: data,
          stepName: attribution.step_name,
          vitals_score: rating,
        });
        break;
      default:
        myAnalyticsTool.track(metricName, { duration: data });
        break;
    }
  },
});

In a world with widely varying device capabilities, a one-size-fits-all event doesn’t always work. Perfume adds data enrichment to all events so we can better understand the real world experiences:

  • deviceMemory: the user's device memory (RAM).
  • hardwareConcurrency: the number of logical CPU processor cores on the user's device.
  • serviceWorkerStatus: status of the service worker between controlled, supported and unsupported.

Based on the Navigator APIs the library can help us differentiate between a low-end and a high-end device/experience:

  • isLowEndDevice: combination of the score of RAM and CPU.
  • isLowEndExperience: combination of the score of RAM, CPU, NetworkStatus and SaveData.

Performance audits

Coo coo coo cool, let's learn something new.

Navigation Timing

Navigation Timing collects performance metrics for the life and timings of a network request.

Perfume helps expose some of the key metrics you might need.

<ul> <li><b>Redirect time</b>: Page redirects aren't totally inconsequential, but they might not be something you run into very often. Still, redirects add latency to requests, so measuring them may be worth the effort.</li> <li><b>DNS lookup</b>: When a user requests a URL, the Domain Name System (DNS) is queried to translate a domain to an IP address.</li> <li><b>Header size</b>: HTTP header size</li> <li><b>Fetch time</b>: Cache seek plus response time</li> <li><b>Worker time</b>: Service worker time plus response time</li> <li><b>Total time</b>: Request plus response time (network only)</li> <li><b>Download time</b>: Response time only (download)</li> <li><b>Time to First Byte</b>: The amount of time it takes after the client sends an HTTP GET request to receive the first byte of the requested resource from the server. It is the largest web page load time component taking 40 to 60% of total web page latency.</li> </ul>
// Perfume.js: navigationTiming { ... timeToFirstByte: 192.65 }

First Paint (FP)

FP is the exact time taken for the browser to render anything as visually different from what was on the screen before navigation, e.g. a background change after a long blank white screen time.

// Perfume.js: fp 1482.00 ms

Navigation Total Blocking Time (NTBT)

This metric measures the amount of time the application may be blocked from processing code during the 2s window after a user navigates from page A to page B. The NTBT metric is the summation of the blocking time of all long tasks in the 2s window after this method is invoked.

Because this library is navigation agnostic, we have this method to mark when the navigation starts.

If this method is called before the 2s window ends; it will trigger a new NTBT measurement and interrupt the previous one.

import { createBrowserHistory } from 'history';
export const history = createBrowserHistory();

initPerfume({
  analyticsTracker: ({ metricName, data }) => {
    myAnalyticsTool.track(metricName, data);
  })
});

// React custom history
history.listen(() => {
  // Measure NTBT at the beginning of each navigation
  perfume.markNTBT();
});

// Perfume.
View on GitHub
GitHub Stars3.2k
CategoryOperations
Updated15d ago
Forks113

Languages

TypeScript

Security Score

100/100

Audited on Mar 6, 2026

No findings