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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@magnolia/pulse

v0.1.0-beta.2

Published

Lightweight Core Web Vitals collector SDK for Magnolia Web Performance. Captures TTFB, FCP, LCP, INP and CLS and beacons them to your Magnolia Web Performance collector region.

Readme

@magnolia/pulse

Pulse is a lightweight JavaScript SDK that captures Core Web Vitals in a visitor's browser and beacons them to your Magnolia Web Performance collector - no RUM instrumentation to build yourself.

What it measures

Pulse wraps Google's web-vitals library to collect all five Core Web Vitals:

| Metric | Name | Category | What it tells you | "Good" threshold | | -------- | ------------------------- | ------------- | --------------------------------------------------------------------------------------------------------- | ---------------- | | TTFB | Time to First Byte | Server | How fast your server responds - the time between the request and the first byte of the response arriving. | ≤ 0.8 s | | FCP | First Contentful Paint | Loading | The exact moment the user sees anything on the screen (text, background image, etc.). | ≤ 1.8 s | | LCP | Largest Contentful Paint | Loading | When the main content - the largest image or text block - finishes rendering. | ≤ 2.5 s | | INP | Interaction to Next Paint | Interactivity | How snappy the page feels when you click or type - the delay until the screen visually updates. | ≤ 200 ms | | CLS | Cumulative Layout Shift | Stability | Whether things jump around while loading (e.g. an ad pops in and pushes the text down). | ≤ 0.1 |

Hard navigations only - no SPA soft-navigation support

Pulse collects one batch of metrics per hard navigation (a real document load - the initial page view, a reload, back/forward, or prerender). It does not re-collect metrics on client-side route changes in single-page apps (SPA "soft navigations" via history.pushState), and that's deliberate, not an oversight:

  • TTFB and FCP are defined off the document's single navigation-timing / first-paint entry and are only ever emitted once per document, full stop - there is no browser-level concept of "TTFB" or "FCP" for a client-side route change.
  • LCP stops accepting new candidates the moment the user has their first interaction (click/keydown) - which includes the very click that triggers a soft navigation. So by the time a new route renders, native LCP tracking for that document is already frozen; there's no "LCP for route 2" available from the standard Performance API.
  • CLS keeps accumulating for the document's entire life regardless of soft navigations, which cuts the other way: an SPA swapping its whole view on a route change can look like one large, unintentional layout shift, inflating the score.
  • INP is the one metric that keeps measuring correctly through soft navigations with no extra work, since interaction/event-timing entries don't care about pushState - it just stays one aggregate number for the whole session rather than being split per route.

nav_type (ours and web-vitals' own Metric.navigationType) reflects only how the hard navigation started (navigate / reload / back_forward / prerender) and never changes afterwards - there is currently no field, on the metric or in the collector schema, distinguishing "from the initial load" vs. "from a subsequent SPA route." The browser's experimental Soft Navigations API is the eventual standards-track answer to this (Chrome-only, still behind flags/origin trial), but it isn't broadly supported yet, so this SDK doesn't build on it. Revisit if/when a real SPA RUM requirement shows up.

Network weight

Alongside the Core Web Vitals, Pulse also reports three network weight readings as additional entries in the same metrics array:

| name | Source | What it tells you | | ------------------- | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------- | | REQUEST_COUNT | Count of resource entries + the document itself | How many separate network requests the page made - a proxy for waterfall/connection complexity. | | TRANSFERRED_BYTES | Sum of transferSize across resource + navigation | Bytes that actually moved over the network this visit (compressed body + headers) - the real bandwidth cost paid. | | RESOURCE_BYTES | Sum of decodedBodySize across resource entries | The decompressed content size - the page's actual weight, independent of compression or caching. |

They're WebVitalsMetric entries like LCP/FCP/etc., just without a rating or delta - there is no industry-standard good/needs-improvement/poor threshold for them, unlike the Core Web Vitals. They do carry nav_type, matching the page's overall navigation type. These are diagnostic/causal numbers: they help explain why a page's Vitals look the way they do (e.g. poor LCP + a high REQUEST_COUNT often points at render-blocking third-party requests), not perceptual ones in their own right.

Two limitations worth knowing:

  • Cross-origin resources under-report. transferSize/decodedBodySize are 0 for any cross-origin resource unless its response sends a Timing-Allow-Origin header permitting it - third-party fonts/ads/analytics that don't opt in are silently missing from TRANSFERRED_BYTES/RESOURCE_BYTES (both are a floor, not an exact total). REQUEST_COUNT is unaffected.
  • The browser's resource-timing buffer caps historical lookups at 250 entries in most browsers. Pulse avoids this by accumulating totals from a live PerformanceObserver as entries happen, rather than reading performance.getEntriesByType('resource') once at flush time - so counts stay accurate on request-heavy pages instead of silently dropping once the buffer fills.

Collection is aggregate-only by design (three numbers, not a per-resource breakdown) to keep the beacon small, and all-or-nothing: on a browser without resource timing support, all three are omitted from the batch entirely rather than sent as misleading zeros.

The numbers are frozen at the load event, not at flush time. flush() still only sends once, on hidden/pagehide like everything else - but for a page that stays in the foreground for a long session (a dashboard left open for hours, say) that could be arbitrarily later than when the page actually finished loading. Without freezing at load, a page that keeps making background requests for the rest of its session - e.g. polling a status endpoint every few seconds - would have these numbers keep growing for as long as the tab stays open, turning "how much did this page load" into "how chatty was this tab session," which isn't the question these metrics are meant to answer. If the page is hidden/unloaded before load ever fires, whatever accumulated so far is reported instead, the same "best effort as of pagehide" behavior as the Core Web Vitals.

Installation

Pulse can be installed two ways, depending on how the host site is built.

npm

npm install @magnolia/pulse
import Pulse from '@magnolia/pulse';

new Pulse({
  key: '<CLIENT KEY GENERATED VIA MAGNOLIA PAAS COCKPIT>',
  region: '<COLLECTOR REGION>',
  metrics: true,
});

<script> tag

For sites that don't run a bundler, load the IIFE build directly - it exposes a global Pulse constructor with the same API:

<script src="https://unpkg.com/@magnolia/pulse/dist/pulse.iife.js"></script>
<script>
  new Pulse({
    key: '<CLIENT KEY GENERATED VIA MAGNOLIA PAAS COCKPIT>',
    region: '<COLLECTOR REGION>',
    metrics: true,
  });
</script>

Configuration

new Pulse({
  key: string, // required
  region: string, // required
  metrics: boolean, // optional, default false
  debug: boolean, // optional, default false
});

| Option | Type | Required | Description | | --------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | key | string | yes | The public collector key for your site, generated in Magnolia PaaS Cockpit. Sent as the X-Vitals-Key header on every beacon - it identifies the site and authorizes ingestion. | | region | string | yes | Which collector region to send beacons to: integration, eu-central, us-east, or ap-southeast. For your region see in Magnolia PaaS Cockpit when the key is created. | | metrics | boolean | no | true collects the full default set of Core Web Vitals above. false (the default) initializes Pulse without collecting anything. | | debug | boolean | no | true logs collection/transport failures to console.error. Defaults to false - Pulse never writes to a real visitor's console unless you opt in while integrating. |

Future: metrics will accept a configuration object instead of a plain boolean, so you can cherry-pick individual metrics or tune sampling. true will keep meaning "everything, at default settings" once that lands.

Browser support & bundle size

Pulse is loaded on every visitor's page, so it's built with a strict size budget in mind:

  • No runtime dependencies beyond web-vitals itself.
  • Tree-shakeable ESM/CJS builds for bundler consumers, plus a minified IIFE build for <script> tag use.
  • Metric collection silently no-ops on browsers that don't support the underlying PerformanceObserver entry types - the page is never blocked or broken.

Development

npm install
npm run build       # emits dist/index.{js,cjs,d.ts} and dist/pulse.iife.js
npm run typecheck
npm test

Project layout:

src/
  index.ts          Pulse class - wiring, buffering, flush lifecycle
  regions.ts        region -> collector base URL map
  network.ts        connection/device/viewport data, with schema-safe fallbacks
  network-weight.ts live-observer request count / transferred / resource bytes tracking
  session.ts        sessionStorage-backed session id, 10 min idle timeout
  send.ts           fetch(keepalive) transport, fully fail-silent
  id.ts             shared id generator (session id, network-weight metric ids)
  types.ts          PulseOptions + collector request/response types

License

See LICENSE.