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

perf-reviewer

v1.0.0

Published

Lightweight frontend performance review SDK — collect runtime signals and generate developer-friendly reports

Downloads

164

Readme

perf-reviewer

A lightweight, focused frontend performance review SDK. Drop it into any web app, collect runtime performance signals, and get a developer-friendly report explaining what looks slow, risky, or worth investigating — without any backend, dashboard, or external service required.


Why this exists

Browser DevTools and Lighthouse are great for manual audits, but they can't tell you what happens in production with real users and real data. perf-reviewer runs inside your app at runtime, observes actual browser signals (Web Vitals, resource timing, long tasks, API durations), and gives you a structured performance report you can inspect, log, or export on demand.

It is not Sentry. It is not a dashboard. It is a developer-focused tool for understanding runtime performance while building and debugging.


Installation

npm install perf-reviewer

Quick start

import { createPerformanceReviewer } from 'perf-reviewer'

const reviewer = createPerformanceReviewer({
  appName: 'shop-web',
  environment: 'development',
})

reviewer.start()

// Track an API call
await reviewer.trackApiCall('load_products', () => fetch('/api/products'))

// Measure an expensive section
await reviewer.measure('render_cart', async () => {
  await renderCart()
})

const report = reviewer.generateReport()
console.log(report)

API

createPerformanceReviewer(config)

Creates a new reviewer instance.

const reviewer = createPerformanceReviewer({
  appName: 'my-app',           // required
  environment: 'production',   // optional
  release: '2.3.0',            // optional
  sessionId: 'custom-id',      // optional — auto-generated if omitted
  thresholds: {                // optional — override any threshold
    slowApiCallMs: 800,
    componentVerySlowRenderMs: 100,
  },
})

reviewer.start()

Begins collection. Captures navigation timing, starts observing resource timing entries and long tasks, and registers Web Vitals callbacks.

reviewer.start()

Call this once, early in your app lifecycle (e.g. in main.ts or _app.tsx).


reviewer.stop()

Disconnects PerformanceObservers. Web Vitals callbacks remain active until the page is unloaded. Calling start() after stop() is not supported — use a new reviewer instance or reset().

reviewer.stop()

reviewer.measure(name, fn, metadata?)

Times an async function and records the result.

const data = await reviewer.measure('fetch_and_render', async () => {
  const res = await fetch('/api/data')
  return res.json()
}, { route: '/checkout' })
  • Returns the original result of fn
  • Records success: true on completion, success: false on thrown errors
  • Rethrows the original error unchanged

reviewer.trackApiCall(name, fetcher, metadata?)

Wraps a fetch-returning function and records timing, HTTP status, and success.

const response = await reviewer.trackApiCall('load_products', () =>
  fetch('/api/products')
)
  • fetcher must return Promise<Response>
  • Returns the original Response
  • Records success: response.ok
  • If fetcher throws, records a failed call and rethrows the original error

reviewer.recordComponentRender(name, durationMs, metadata?)

Records a component render duration manually. Designed for use in custom React Profiler adapters or framework instrumentation.

reviewer.recordComponentRender('ProductGrid', 42, { itemCount: 120 })

reviewer.generateReport()

Produces a PerformanceReport snapshot from all collected data.

const report = reviewer.generateReport()

The report is a plain JSON-serializable object. See Report structure below.


reviewer.reset()

Clears all collected records (API calls, measurements, component renders, Web Vitals, navigation timing, resource timing, long tasks) and resets the running state.

reviewer.reset()
reviewer.start() // safe to start fresh after reset

Report structure

generateReport() returns a PerformanceReport:

{
  appName: string
  environment?: string
  release?: string
  sessionId: string
  generatedAt: number             // Unix timestamp (ms)

  score: number                   // 0–100
  rating: 'good' | 'needs-improvement' | 'poor'

  summary: string[]               // 2–5 human-readable summary lines

  webVitals: {
    cls?: MetricReview
    lcp?: MetricReview
    inp?: MetricReview
    fcp?: MetricReview
    ttfb?: MetricReview
  }

  navigation?: {
    domContentLoadedMs?: number
    loadEventMs?: number
    responseTimeMs?: number
    transferSize?: number
  }

  apiCalls: {
    total: number
    failed: number
    slow: number
    verySlow: number
    averageDurationMs?: number
    slowest: ApiCallRecord[]      // top 5, sorted by duration desc
    failures: ApiCallRecord[]
  }

  measurements: {
    total: number
    failed: number
    slow: number
    verySlow: number
    averageDurationMs?: number
    slowest: MeasurementRecord[]  // top 5
    failures: MeasurementRecord[]
  }

  components: {
    total: number
    slow: number
    verySlow: number
    slowest: ComponentRenderRecord[]  // top 5
    byName: Array<{
      name: string
      renderCount: number
      averageDurationMs: number
      maxDurationMs: number
    }>
  }

  resources: {
    totalTracked: number          // capped at 20 (slowest kept)
    slow: number
    verySlow: number
    slowest: ResourceTimingRecord[]
  }

  longTasks: {
    totalTracked: number          // capped at 20 (slowest kept)
    slowest: LongTaskRecord[]
  }

  recommendations: string[]
}

Each MetricReview:

{
  value: number
  rating: 'good' | 'needs-improvement' | 'poor'
  description: string             // human-readable explanation
}

Score algorithm

The score starts at 100 and deductions are applied:

| Signal | Per item | Cap | |---|---|---| | Poor Web Vital | −15 | — | | Needs-improvement Web Vital | −7 | — | | Failed API call | −5 | −20 | | Slow API call | −3 | −15 | | Very slow API call | −6 | −24 | | Failed measurement | −4 | −16 | | Slow measurement | −2 | −10 | | Very slow measurement | −5 | −20 | | Slow component render | −2 | −10 | | Very slow component render | −5 | −20 | | Long task | −3 | −15 | | Very slow resource | −3 | −15 |

Score is clamped to [0, 100].

| Score | Rating | |---|---| | ≥ 90 | good | | 60–89 | needs-improvement | | < 60 | poor |


Default thresholds

| Threshold | Default | |---|---| | slowApiCallMs | 1000 ms | | verySlowApiCallMs | 2500 ms | | slowMeasureMs | 100 ms | | verySlowMeasureMs | 500 ms | | slowResourceMs | 1000 ms | | verySlowResourceMs | 2500 ms | | longTaskMs | 50 ms | | lcpGoodMs | 2500 ms | | lcpNeedsImprovementMs | 4000 ms | | inpGoodMs | 200 ms | | inpNeedsImprovementMs | 500 ms | | clsGood | 0.1 | | clsNeedsImprovement | 0.25 | | ttfbGoodMs | 800 ms | | ttfbNeedsImprovementMs | 1800 ms | | componentSlowRenderMs | 16 ms | | componentVerySlowRenderMs | 50 ms |

Override any subset via the thresholds config option.


Limitations

  • Not Lighthouse. Lighthouse audits static resources and simulated load. perf-reviewer observes real-time browser signals during actual usage — different data, different purpose.
  • Not Sentry. There is no error tracking, user session replay, or backend reporting. This is purely a local, in-memory signal collector.
  • Recommendations are hints, not root causes. The library observes signal patterns and surfaces likely areas to investigate. It cannot know why something is slow without deeper profiling.
  • Component tracking is manual. There is no automatic React/Vue/Svelte integration yet. You call recordComponentRender() yourself, or build a thin adapter on top.
  • Web Vitals are async. LCP and CLS are reported lazily by the browser. Calling generateReport() immediately after start() may show no vitals yet.
  • SSR-safe. The SDK checks for browser API availability before every call and fails silently in server-side rendering contexts.

Roadmap

  • React Profiler adapter (usePerformanceReviewer)
  • Automatic fetch instrumentation (opt-in, no monkey-patching by default)
  • report.toHTML() — render report as a readable HTML page
  • Browser console pretty-printer (reviewer.printReport())
  • JSON export / import for comparing reports across sessions
  • Web Vitals attribution (element references, selector hints)
  • Historical comparison between two PerformanceReport snapshots
  • CI-friendly browser test example (Playwright + perf-reviewer)

License

MIT