perceived-perf
v1.0.0
Published
Measure user-perceived performance between interaction and visible UI response
Maintainers
Readme
perceived-perf
Measure user-perceived performance in frontend applications: the time between a user interaction (click, action) and the visible UI response, not raw render or network timings.
What is user-perceived performance?
User-perceived performance is how fast the app feels to the user. It’s the delay from “I clicked” to “I see the result.” That includes:
- Your async work (e.g. API calls, state updates)
- The browser’s layout and paint
- The moment the screen actually updates
This library measures that full span so you can keep it under control (e.g. < 100ms) and avoid sluggish UX.
Why this library?
- UX-focused: Measures interaction → visible response, not low-level timings.
- Dev-only: Automatically no-ops in production; no runtime cost or logs.
- Framework-agnostic core: Use with any stack; optional React helpers.
- Zero runtime dependencies: Small, tree-shakable, and easy to reason about.
Install
npm install perceived-perfUsage
Core: wrap an async action
import { trackInteraction, getInteractionMetrics } from "perceived-perf";
const data = await trackInteraction(
"fetch-user",
() => fetch("/api/user").then((r) => r.json()),
{ thresholdMs: 100 }
);
// Later: inspect recorded metrics (e.g. in dev tools or tests)
const metrics = getInteractionMetrics();
// [{ name: "fetch-user", start, end, duration, status: "success" }]If the measured duration exceeds thresholdMs, a single console warning is emitted in development.
Core: manual start/end
import { startInteraction, endInteraction } from "perceived-perf";
startInteraction("open-modal");
await doSomething();
// After UI has updated, end is recorded after next paint
endInteraction("open-modal");React: track click → next paint
import { trackClick } from "perceived-perf/react";
function SubmitButton() {
return (
<button {...trackClick("submit")}>Submit</button>
);
}To run your own handler and still track, call the returned onClick first:
<button
onClick={(e) => {
trackClick("submit").onClick(e);
handleSubmit();
}}
>
Submit
</button>trackClick("submit") returns { onClick }. On click it starts the interaction and ends it after the next paint, so the metric reflects “click → visible response.”
Dev-only behavior
- Production: All tracking is disabled (
NODE_ENV !== "development"). No timers, no store writes, no console output. - Development: Metrics are kept in memory; no analytics backend or network. Console is used only when a run exceeds
thresholdMs.
So you can leave trackInteraction / trackClick in your code and ship without any performance or logging impact.
Example output
When a run exceeds the threshold in development:
[perceived-perf] "fetch-user" exceeded threshold (142ms > 100ms)Recorded metric (from getInteractionMetrics()):
{
name: "fetch-user",
start: 123456.78,
end: 123598.92,
duration: 142.14,
status: "success"
}API
| API | Description |
|-----|-------------|
| trackInteraction(name, fn, options?) | Runs fn(), waits for resolution + next paint, records duration, warns if > thresholdMs. Returns fn’s result. |
| startInteraction(name) | Starts a manual interaction. |
| endInteraction(name) | Ends it (recorded after next paint). |
| getInteractionMetrics() | Returns a copy of all recorded metrics. |
React (from perceived-perf/react):
| API | Description |
|-----|-------------|
| trackClick(name) | Returns { onClick }; on click, starts interaction and ends after next paint. |
License
MIT
