Repository navigation
Support non-React Storybook frameworks (Web Components, Lit, Vue, etc.) #48
Description
Problem
The addon currently couples metric collection to React via the withPerformanceMonitor decorator, which uses React Profiler and expects stories to return React elements. This means:
- Loading the default preview export in a non-React Storybook (e.g.
@storybook/web-components-vite) causes rendering errors because the decorator wraps stories in React Profiler components. - The only workaround is loading the addon via /preset (manager panel only) and writing a custom decorator from scratch that reimplements metric collection and channel communication.
We ran into this integrating the addon into Spectrum Web Components (Storybook 10 + @storybook/web-components-vite). Our workaround was a ~600-line custom decorator using web platform APIs (requestAnimationFrame, PerformanceObserver, MutationObserver, performance.memory) to feed metrics to the existing manager panel.
Suggestion
Separate the addon into framework-agnostic and framework-specific layers:
- Core metrics collector (framework-agnostic) -- The web platform metrics (frame timing, long tasks, CLS, event timing / INP, LoAF, memory, DOM element count) don't depend on React at all. These could live in a shared base class or module.
- Framework-specific adapters -- React Profiler metrics (
reactRenderCount, reactMountDuration, etc.) are React-specific and should be an optional layer on top. Other frameworks could provide their own equivalents (e.g. Lit's updateComplete timing for web components, Vue's renderTracked/renderTriggered). - Pluggable decorator export -- Instead of a single React-only decorator, export a factory or base decorator that frameworks can extend:
// Possible API shape
import { createPerformanceDecorator, WebPlatformCollector } from '@github-ui/storybook-addon-performance-panel/core';
// Web components -- just web platform metrics
export const withPerformanceMonitor = createPerformanceDecorator({
collector: new WebPlatformCollector(),
});
// React -- web platform + React Profiler
import { ReactProfilerCollector } from '@github-ui/storybook-addon-performance-panel/react';
export const withPerformanceMonitor = createPerformanceDecorator({
collector: new CompositeCollector([new WebPlatformCollector(), new ReactProfilerCollector()]),
});- Metrics schema -- The manager panel could gracefully handle missing metric groups (e.g. hide the React Profiler section when those metrics aren't present) rather than requiring every field to be populated with zeroes.
What I have built as a workaround
Our custom decorator collects these metrics using browser-native APIs:
Link to the gist: https://gist.github.com/Rajdeepc/80215eae8de1d764a176e4411e17f9cc
| Metric | API |
|---|---|
| FPS, frame time, dropped frames | requestAnimationFrame loop |
| Long tasks, TBT | PerformanceObserver (longtask) |
| Layout shifts (CLS) | PerformanceObserver (layout-shift) |
| Input latency, INP | PerformanceObserver (event) |
| Long animation frames (LoAF) | PerformanceObserver (long-animation-frame) |
| Memory usage | performance.memory |
| DOM element count | MutationObserver |
All of this is framework-agnostic and could be the shared foundation. Happy to contribute a PR if the architecture direction makes sense.
Activity
Rajdeepc commentedon Feb 23, 2026
@mattcosta7 Looping in your PR here so we can consolidate the discussion and align on a refined approach within a single thread. This should help us evaluate trade-offs clearly and converge on a well-reasoned direction.
mattcosta7 commentedon Feb 23, 2026
@Rajdeepc - thanks for opening this! I agree with your proposal for the decorator factor as a method of enabling specific performance tracking per framework, and think we should work towards that approach (as well as controlling which panels are added there)
I think #41 is a good step towards that while unblocking usage outside of react first, and a followup to implement the factory approach makes sense? Assuming that works for ya'll too
mattcosta7 commentedon Feb 24, 2026
The first steps to this shipped in
v1.1.0which has a/universalexport that supports non-react environmentsWe'll continue making this more pluggable for non-react framework level metrics