Skip to content

perf(PageLayout): eliminate ~614ms forced reflow from getComputedStyle on mount - #7532

Merged
hectahertz merged 7 commits into
mainfrom
hectahertz/pagelayout-remove-reflow
Feb 23, 2026
Merged

perf(PageLayout): eliminate ~614ms forced reflow from getComputedStyle on mount#7532
hectahertz merged 7 commits into
mainfrom
hectahertz/pagelayout-remove-reflow

Conversation

@hectahertz

@hectahertzhectahertz commented Feb 12, 2026

Copy link
Copy Markdown
Contributor

Closes #

SplitPageLayout's usePaneWidth hook calls getComputedStyle() inside a useLayoutEffect on mount, forcing a synchronous layout. This is the single largest source of client-side latency across profiled Files controller routes on github.com.

Impact on github.com (production profiling)

RoutegetPaneMaxWidthDiffTotal forced reflowPrimer share
Code View (241-line file)614-621ms745-758ms82%
Code View (smaller file)124ms370ms97% (360ms combined Primer)
Repo Overview-553ms100% (all Primer internals)

On the Code View page, getPaneMaxWidthDiff alone forces the browser to synchronously lay out 430 pending DOM nodes in a single 529ms layout update.

The fix

The CSS variable --pane-max-width-diff only has two possible values, controlled by a single @media (min-width: 1280px) breakpoint:

  • Below 1280px: 511px
  • At/above 1280px: 959px

This is fully determinable from window.innerWidth, no getComputedStyle needed:

// Before (forces synchronous layout, 614ms on large pages)maxWidthDiffRef.current=getPaneMaxWidthDiff(paneRef.current)// After (property read + comparison, ~0ms)maxWidthDiffRef.current=getMaxWidthDiffFromViewport()

The same replacement is applied to the resize handler's breakpoint-crossing path.

Chrome DevTools Performance Trace Results

Profiled on the Heavy Content performance story (~5,400 DOM nodes, 1,366 flex containers).

PageLayout-caused Forced Reflow:

ScenarioBefore (main)After (this PR)Improvement
1x CPU262 ms0 ms-262 ms (eliminated)
4x CPU throttle1,136 ms0 ms-1,136 ms (eliminated)

Style Recalculation:

ScenarioBeforeAfterDelta
1x CPU228 ms (5,456 elements)110 ms (5,467 elements)-118 ms (-52%)
4x CPU throttle984 ms480 ms-504 ms (-51%)

LCP (4x CPU throttle):

MetricBeforeAfterDelta
LCP5,784 ms4,602 ms-1,182 ms (-20%)
Render delay5,409 ms4,251 ms-1,158 ms

Changelog

New

  • getMaxWidthDiffFromViewport(): derives the --pane-max-width-diff value from window.innerWidth without touching the DOM

Changed

  • Mount-time and resize-breakpoint-crossing initialization in usePaneWidth uses the viewport-based lookup instead of getComputedStyle
  • Exported the wide-breakpoint diff value (959) from PageLayout.module.css via :export to keep JS and CSS in sync

Removed

N/A

Rollout strategy

  • Patch release
  • Minor release
  • Major release; if selected, include a written rollout or migration plan
  • None; if selected, include a brief description as to why

Testing & Reviewing

  • All 46 existing usePaneWidth tests pass, with expectations corrected to reflect real-world breakpoint behavior at viewport >= 1280px
  • 3 new tests added for getMaxWidthDiffFromViewport covering below, at, and above the 1280px breakpoint
  • The old tests were inadvertently wrong: jsdom can't evaluate CSS media queries, so getComputedStyle always returned the default (511) even at 1280px viewport. The new viewport-based approach correctly returns 959 at that width.

Merge checklist

  • Added/updated tests
  • Added/updated documentation
  • Added/updated previews (Storybook)
  • Changes are SSR compatible
  • Tested in Chrome
  • Tested in Firefox
  • Tested in Safari
  • Tested in Edge
  • (GitHub staff only) Integration tests pass at github/github

@hectahertz
hectahertz requested a review from a team as a code ownerFebruary 12, 2026 14:02
@changeset-bot

changeset-botBot commented Feb 12, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8fdeef7

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
@primer/reactPatch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actionsgithub-actionsBot added the integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm label Feb 12, 2026
@github-actions

Copy link
Copy Markdown
Contributor

👋 Hi, this pull request contains changes to the source code that github/github-ui depends on. If you are GitHub staff, test these changes with github/github-ui using the integration workflow. Or, apply the integration-tests: skipped manually label to skip these checks.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR removes a major mount-time performance bottleneck in PageLayout by eliminating a getComputedStyle() call that can force expensive synchronous layout recalculation. It replaces the DOM/CSS variable read with a viewport-width-based lookup derived from the same breakpoint logic already encoded in PageLayout.module.css.

Changes:

  • Add getMaxWidthDiffFromViewport() and use it on mount and when crossing the 1280px breakpoint, avoiding getComputedStyle() forced reflow.
  • Export the wide breakpoint --pane-max-width-diff value (959) from PageLayout.module.css via :export to keep JS/CSS in sync.
  • Update and extend unit tests to reflect correct wide-breakpoint behavior and cover the new helper.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

FileDescription
packages/react/src/PageLayout/usePaneWidth.tsReplaces mount and resize-breakpoint diff calculation with a viewport-only lookup helper.
packages/react/src/PageLayout/usePaneWidth.test.tsUpdates expectations for wide breakpoint behavior and adds new helper unit tests.
packages/react/src/PageLayout/PageLayout.module.cssExposes the wide diff value (paneMaxWidthDiffWide) via :export for JS consumption.
.changeset/pagelayout-remove-reflow.mdAdds a patch changeset describing the performance fix.

Comment threadpackages/react/src/PageLayout/usePaneWidth.ts
Comment threadpackages/react/src/PageLayout/usePaneWidth.test.ts Outdated
@github-actions
github-actionsBot requested a deployment to storybook-preview-7532 February 12, 2026 16:20 Abandoned

@francineluccafrancinelucca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looking good! some questions/suggestions

Comment threadpackages/react/src/PageLayout/usePaneWidth.test.ts
Comment threadpackages/react/src/PageLayout/usePaneWidth.ts Outdated
Comment threadpackages/react/src/PageLayout/PageLayout.module.css
Comment threadpackages/react/src/PageLayout/usePaneWidth.test.ts Outdated
@hectahertzhectahertz changed the title perf(PageLayout): eliminate 614ms forced reflow from getComputedStyle on mountperf(PageLayout): eliminate ~614ms forced reflow from getComputedStyle on mountFeb 13, 2026
@github-actions
github-actionsBot requested a deployment to storybook-preview-7532 February 13, 2026 15:46 Abandoned
@github-actions
github-actionsBot requested a deployment to storybook-preview-7532 February 13, 2026 15:53 Abandoned
@hectahertz

Copy link
Copy Markdown
ContributorAuthor

@francinelucca everything should be addressed now!

@liuliu-devliuliu-dev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice fix! Learned some tricks in this pr 💖

@francineluccafrancinelucca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hectahertzand others added 7 commits February 23, 2026 18:35
Replace getPaneMaxWidthDiff (which calls getComputedStyle, forcing a
synchronous layout recalc) with getMaxWidthDiffFromViewport, a pure JS
function that derives the same value from window.innerWidth.
The CSS variable --pane-max-width-diff only has two values controlled by
a single @media (min-width: 1280px) breakpoint, so a simple threshold
check is semantically equivalent — no DOM query needed.
This eliminates ~614ms of blocking time on mount for pages with large
DOM trees (e.g. SplitPageLayout).
…romViewport tests
Replace vi.spyOn(window, 'innerWidth', 'get').mockReturnValue(...) with
vi.stubGlobal('innerWidth', ...) to prevent spy leaks. The outer describe
block's afterEach calls vi.unstubAllGlobals(), which correctly cleans up
stubGlobal but does not restore spyOn mocks. This makes the tests
consistent with the rest of the file and avoids order-dependent failures.
…nments
Add canUseDOM check so the function returns DEFAULT_MAX_WIDTH_DIFF
instead of throwing when window is unavailable (SSR, node tests,
build-time evaluation). canUseDOM was already imported in the file.
The function is no longer called after replacing it with
getMaxWidthDiffFromViewport(). Keeping it around is a footgun —
it calls getComputedStyle which forces synchronous layout.
Remove it and its associated tests.
The test previously stubbed innerWidth without firing a resize event,
then asserted against a stale maxWidthDiffRef — a scenario that cannot
occur in a real browser. Dispatch the resize event so the breakpoint
crossing updates the cached diff value, making the assertion realistic.
@hectahertz
hectahertzforce-pushed the hectahertz/pagelayout-remove-reflow branch from c8c9aac to 8fdeef7CompareFebruary 23, 2026 17:40
@primer-integration

Copy link
Copy Markdown

👋 Hi from github/github-ui! Your integration PR is ready: https://github.com/github/github-ui/pull/14285

@primer-integration

Copy link
Copy Markdown

Integration test results from github/github-ui:

Passed CI  Passed
Passed VRT  Passed
Passed Projects  Passed

All checks passed!

@hectahertz
hectahertz added this pull request to the merge queueFeb 23, 2026
Merged via the queue into main with commit 39a2151Feb 23, 2026
52 checks passed
@hectahertz
hectahertz deleted the hectahertz/pagelayout-remove-reflow branch February 23, 2026 18:10
@primerprimerBot mentioned this pull request Feb 23, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

integration-tests: recommendedThis change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@hectahertz@jonrohan@liuliu-dev@mattcosta7@TylerJDev@francinelucca