npm version License: MIT Node.js
A performance monitoring library for tracking user interactions using Interaction to Next Paint (INP) and Long Animation Frames (LoAF). Features declarative trace signing and pluggable reporting.
npm install @doist/interaction-trace
import { initInteractionTraceMonitor } from '@doist/interaction-trace' const cleanup = initInteractionTraceMonitor({ reporter: (report) => { // Send to your analytics service console.log('Trace report:', report) }, enrollment: { sampleRate: 10, // 10% of sessions isEnabled: () => { if (user.isInternal) return true // force enable for internal users return undefined // everyone else: use sampleRate }, }, abortSignal: controller.signal, // Optional: auto-cleanup when aborted }) // Or call cleanup() manually when done
A trace measures the time from a user click to the browser completing all visual updates. Traces are created automatically on pointerup events—use signInteractionTrace() to name and provide details for the pending trace.
import { signInteractionTrace } from '@doist/interaction-trace' button.addEventListener('click', () => { signInteractionTrace('open modal', { modalId: 'settings' }) openModal() })
Create a pre-typed function for compile-time validation of trace names and details:
import { signInteractionTrace } from '@doist/interaction-trace' type MyTraces = { 'open modal': { modalId: string } 'submit form': { formId: string } } const signAppTrace = signInteractionTrace.withTypes<MyTraces>() signAppTrace('open modal', { modalId: 'settings' }) // ✅ Works signAppTrace('opne modal', { modalId: 'settings' }) // ❌ Error: typo caught signAppTrace('open modal', { formId: 'x' }) // ❌ Error: wrong details
import { useInteractionTrace } from '@doist/interaction-trace/react' function SettingsModal() { useInteractionTrace('open modal', { modalId: 'settings' }) return <div>...</div> }
import { useInteractionTrace } from '@doist/interaction-trace/react' type MyTraces = { 'open modal': { modalId: string } 'submit form': { formId: string } } const useAppTrace = useInteractionTrace.withTypes<MyTraces>() function SettingsModal() { useAppTrace('open modal', { modalId: 'settings' }) return <div>...</div> }
| Option | Type | Description |
|---|---|---|
sampleRate |
number |
Percentage of sessions to enroll (0-100) |
persistKey |
string |
sessionStorage key for enrollment state. Default: 'interaction-trace-enrolled' |
isEnabled |
() => boolean | undefined |
Override function. true/false override sampling, undefined defers to sampleRate |
The reporter receives a TraceReport object:
type TraceReport = { id: string // Unique trace ID (crypto.randomUUID()) duration: number // Total LoAF duration (ms) inp: number | undefined // INP value if captured (ms) details: { name: string // Trace name from signInteractionTrace() [key: string]: unknown // Additional details } device: { memoryGB: string // Bucketed: "0.25-2", "3-7", "8-plus", or "unknown" cpuCores: string // Bucketed: "1-8", "9-16", "17-plus" } }
Requires Chrome 123+ for Long Animation Frames and Event Timing APIs. In unsupported browsers, the monitor silently no-ops—your app continues to work normally, just without trace collection.
This library only tracks pointer-based interactions (pointerup events). Keyboard-initiated interactions (e.g., pressing Enter to submit a form, Space to toggle a checkbox) are not captured. INP metrics will only reflect mouse/touch interactions.
If your application has significant keyboard usage, consider this when interpreting the collected metrics.
Development requires Node.js >= 22.18.0 (see .node-version).
npm install
npm run build
npm testThis project uses release-please to automate releases. Commits merged to main with fix: trigger patch releases, feat: triggers minor releases, and feat!: or fix!: triggers major releases.
When commits land on main, release-please creates a release PR. Merging it publishes to npm and GitHub Packages.
Released under the MIT License.