npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

web-vitals-reporter

v1.0.0

Published

Report Web Vitals to your API with one POST request per session

Downloads

6,076

Readme

web-vitals-reporter

Report Web Vitals to your API with one POST request per session.

The web-vitals is a small and powerful library that accurately measures Web Vitals. It has no opinion on how to report data from a browser to your analytics. It results in multiple API calls, session tracking, and lost metrics. The web-vitals-reporter makes Web Vitals reporting as simple as sending one POST request.

Features:

  • Report Web Vitals with one request per session;
  • Associate useful device information like the number of cpus, memory size, and connection type;
  • Report custom front end metrics;
  • Handle edge-cases like multiple CLS calls, round values, and sendBeacon fallback;
  • It's a tiny (800 bytes) library without external dependencies.

Usage

① Report Core Web Vitals and device information to an API endpoint:

import { getCLS, getFID, getLCP } from 'web-vitals'
import { createApiReporter, getDeviceInfo } from 'web-vitals-reporter'

// Init report callback with information about the browser.
const sendToAnalytics = createApiReporter('/analytics', { initial: getDeviceInfo() })

// Setup web-vitals
getLCP(sendToAnalytics)
getFID(sendToAnalytics)
getCLS(sendToAnalytics)

// Receive `POST /analytics` at the end of the session:
{
  id: '1591874402350-8969370227936',
  cpus: 8,
  memory: 8,
  connection: {rtt: 100, downlink: 5, effectiveType: '4g'},
  LCP: 1487,
  FID: 6,
  CLS: 1.5602,
  duration: 4560 // session duration
}

② Measure performance with Next.js:

import { createApiReporter } from 'web-vitals-reporter'

// init reporter
const report = createApiReporter('/analytics')

// export `reportWebVitals` custom function
export function reportWebVitals(metric) {
  if (metric.label === 'web-vitals') {
    report(metric)
  } else {
    report({ name: metric.name, value: metric.value })
  }
}

// or just, `report` supports custom metrics:
export { report as reportWebVitals }

③ Load and report Web Vitals using a <script> tag:

<script defer src="https://unpkg.com/web-vitals"></script>
<script defer src="https://unpkg.com/web-vitals-reporter"></script>
<script>
  addEventListener('DOMContentLoaded', function () {
    var sendToAnalytics = webVitalsReporter.createApiReporter('/analytics')
    webVitals.getCLS(sendToAnalytics)
    webVitals.getFID(sendToAnalytics)
    webVitals.getLCP(sendToAnalytics)
  })
</script>

API

createApiReporter(url, [options])

Create a report function that accepts Web Vitals' Metric object. At the end of the session, it sends collected data to the url using a POST request.

It accepts any { name: string, value: number } object, making it a useful tool for reporting any metric to the API using the one-request-per-session pattern.

options.initial

Use initial to add extra context to the result object. By default web-vitals-reporter only adds id and session duration. It's possible to rewrite id with the initial object.

import { getFID } from 'web-vitals'
import { createApiReporter, getDeviceInfo } from 'web-vitals-reporter'

const report = createApiReporter('/analytics', {
  initial: { id: 'custom-id', cpus: getDeviceInfo().cpus },
})

getFID(report)

// reported body:
{
  id: 'custom-id',
  cpus: 8,
  FID: 24,
  duration: 4560 // session duration
}

options.onSend(url, result)

By default web-vitals-reporter uses sendBeacon and fallbacks to XMLHttpRequest.

Use onSend to implement a custom request logic, like logging data in development, or adding extra headers with window.fetch.

import { createApiReporter } from 'web-vitals-reporter'

// detect Lighthouse using an `userAgent`
const isLighthouse = Boolean(navigator.userAgent.match('Chrome-Lighthouse'))

// exclude `localhost`
const isLocalhost = location.origin.includes('localhost')

// don't send results to API when a page tested with Lighthouse
const report = createApiReporter('/analytics', {
  onSend:
    isLighthouse || isLocalhost
      ? (url, result) => {
          console.log(JSON.stringify(result, null, '  '))
        }
      : null,
})

To see output in the console, set Preserve log option and refresh the page.

web vitals reporter

options.mapMetric(metric, result)

By default web-vitals-reporter only rounds metric.value for known Web Vitals (code).

Use mapMetric to implement a custom metric mapping. For example:

import { getCLS, getFID, getLCP } from 'web-vitals'
import { createApiReporter } from 'web-vitals-reporter'

const report = createApiReporter('/analytics', {
  mapMetric: (metric) => {
    switch (metric.name) {
      // capture LCP element and its size
      case 'LCP': {
        const entry = metric.entries[metric.entries.length - 1] // use the last
        return {
          largestContentfulPaint: metric.value,
          largestContentfulElement: getCssSelector(entry.element), // custom helper
          largestContentfulElementSize: entry.size,
        }
      }

      // capture cumulative/largest/total layout shift
      case 'CLS': {
        return {
          cumulativeLayoutShift: metric.value,
          largestLayoutShift: Math.max(...metric.entries.map((e) => e.value)),
          totalLayoutShifts: metric.entries.length,
        }
      }

      // report more information about first input
      case 'FID': {
        const entry = metric.entries[0]
        return {
          firstInputDelay: metric.value,
          firstInputName: entry.name,
          firstInputTime: entry.startTime,
        }
      }

      // default name –> value mapping
      default:
        return { [metric.name]: metric.value }
    }
  },
})

getLCP(report)
getFID(report)
getCLS(report)

options.beforeSend(result)

Use beforeSend to modify the final result before it's sent to the server. Note: The method should be synchronous because it's fired at the end of the session when the tab is closed.

Example, compute metric score to pass Core Web Vitals thresholds:

import { getCLS, getFID, getLCP } from 'web-vitals'
import { createApiReporter } from 'web-vitals-reporter'

const report = createApiReporter('/analytics', {
  beforeSend: (result) => {
    const { LCP, FID, CLS } = result
    if (!LCP || !FID || !CLS) return // Core Web Vitals are not supported

    // return extra attributes to merge into the final result
    return {
      LCPScore: LCP < 2500 ? 'good' : LCP < 4500 ? 'needs improvement' : 'poor'
      FIDScore: FID < 100 ? 'good' : FID < 300 ? 'needs improvement' : 'poor'
      CLSScore: CLS < 0.1 ? 'good' : CLS < 0.25 ? 'needs improvement' : 'poor'
    }
  },
})

getLCP(report)
getFID(report)
getCLS(report)

// Receive `POST /analytics` at the end of the session with:
{
  id: '1591874402350-8969370227936',
  LCP: 1487,
  LCPScore: 'good',
  FID: 106,
  FIDScore: 'needs improvement'
  CLS: 1.5602,
  CLSScore: 'poor'
}

getDeviceInfo()

A helper that returns device information (connection type, memory size, or the number of CPU cores). Use these data to add dimensions to your analytics.

import { getDeviceInfo } from 'web-vitals-reporter'
console.log(getDeviceInfo())

// printed in console:
{
  "url": "https://treo.sh/",
  "referrer": "https://github.com/",
  "userAgent": "Mozilla/5.0 ...",
  "cpus": 8,
  "memory": 8,
  "connection": { "rtt": 100, "downlink": 5, "effectiveType": "4g" }
}

Return types:

{
  // The page URL from `location.href`.
  url?: string,

  // The referrer value from `document.referrer`.
  // It's useful to detect unique visits, without cookies or fingerprinting
  // https://docs.simpleanalytics.com/uniques
  referrer?: string,

  // The value of `navigator.userAgent` for browser detection
  userAgent?: string,

  // An approximate amount of device memory in gigabytes:
  // https://developer.mozilla.org/en-US/docs/Web/API/Navigator/deviceMemory
  memory?: number,

  // The number of CPU cores:
  // https://developer.mozilla.org/en-US/docs/Web/API/NavigatorConcurrentHardware/hardwareConcurrency
  cpus?: number,

  // The network information:
  // https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation
  connection?: {
    effectiveType: string,
    rtt: number,
    downlink: number,
  },
}

Credits

Sponsored by Treo.sh - Page speed monitoring made simple.