Why it matters
Scroll position, scroll-spy or in-view state held in React turns every scroll event into a render and commit on the main thread, the same thread that must produce each scroll frame. It costs little on a fast laptop and drops frames on a phone.
How to fix it
Keep scroll position out of React state: drive the effect with CSS scroll-driven animations or IntersectionObserver, write to a ref and update the DOM in requestAnimationFrame, or subscribe with useSyncExternalStore and only emit when a threshold is crossed.
Example
// before: const [y, setY] = useState(0); onScroll = () => setY(scrollY)
// after: only re-render when the header state actually flips
const scrolled = useSyncExternalStore(subscribeScroll, () => scrollY > 80);References
More smoothness rules
void smooth reports 18 rules in this category. How the page feels after it loads: dropped frames during a scripted scroll, long animation frames, INP of real clicks, shifts after input, animated layout properties, idle rAF loops and reduced-motion handling.
reduced-motion-ignoredAnimations keep running with prefers-reduced-motionraf-loop-idlerequestAnimationFrame loop runs while idlescroll-timeline-no-fallbackContent is invisible without scroll-driven-animation supportmultiple-webgl-contextsMultiple WebGL contextsreact-wasted-rendersReact component re-renders with unchanged propsreact-render-stormOne React commit re-renders hundreds of componentsreact-idle-commitsReact keeps committing while the page is idlereact-no-namesReact component names are minified