Skip to content

Performance regression with 6.4.0 #5730

@slipset

Description

@slipset

We recently upgraded from 6.1.17 to 6.4.0 and saw performance degradations. Reverting back to 6.1.17 fixed the performance problems we were seeing. We were able to capture a profiling snapshot which shows a lot of time being spent in CSS. What follows is Claudes analysis of the problem. We're not claiming it's correct, but it seems plausible.

Feeding this snapshot to Claude, we got from it:

Here's what the profiler data shows:

Root cause: styled-components upgrade 6.1.17 → 6.4.0

Profiler evidence:

  • insertRule — 23.0% of all CPU time (self)
  • deleteRule — 13.0% of all CPU time (self)
  • Both called via styled-components' rebuildGroup path: rebuildGroup → clearRules/insertRules
  • Together they consume 36% of total CPU — far and away the dominant cost
    What's happening: styled-components 6.4.0's rebuildGroup is clearing and re-inserting all CSS rules in a style group on every React commit cycle (mount/unmount of styled components). The call stacks show
    renderStyles and removeStyles triggering rebuildGroup during React's layout effect phase (commitHookEffectListMount/commitHookEffectListUnmount).

When we asked Claude further to find the commit which introduced this regression, it found:

The bug was introduced in commit 5b045df — "fix: shared-group global styles and eager keyframes registration (#5696)", authored by Evan Jacobs (quantizor), March 21 2026. It shipped in 6.4.0.

The architectural change:

6.1.17 — each createGlobalStyle instance gets its own stylesheet group (componentId + instance). Rendering or unmounting one instance only touches that instance's CSS rules.

6.4.0 — all instances of a createGlobalStyle component share ONE group (componentId). The new rebuildGroup method clears ALL rules in the shared group and re-inserts every surviving instance's rules. This was
done to fix instances stepping on each other on unmount (#5695, #3146, #3008).

Why it's expensive: The useLayoutEffect in createGlobalStyle has props in its dependency array. For dynamic global styles, props is a new object every render, so the effect fires every render cycle. Each cycle:

  1. Cleanup runs: removeStyles → rebuildGroup → clearRules (delete all rules in group) + insertRules (re-insert all remaining instances)
  2. New effect runs: renderStyles → rebuildGroup → clearRules (delete all again) + insertRules (re-insert all including updated one)

That's two full group rebuilds per render, each doing N deleteRule + N insertRule calls via CSSStyleSheet CSSOM APIs. In 6.1.17 each render only cleared+inserted rules for the single instance that changed.

Regular styled.div-style components are not affected — they use ComponentStyle.generateAndInjectStyles which has a hasNameForId guard and never calls clearRules.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions