perf(site): memoize chat rendering hot path#23720
Merged
Conversation
- Hoist createComponents to module scope in response.tsx so every Response instance shares stable component references. Previously, each render of each Response created a fresh components map, forcing Streamdown to discard its cached render tree. - Wrap StickyUserMessage in memo(). Profiling showed it as the #1 render bottleneck at 35.7% of component self time. Each instance carries IntersectionObserver + ResizeObserver + scroll handlers. - Wrap ConversationTimeline in memo() to prevent cascade re-renders from the parent when props are stable. - Remove duplicate buildSubagentTitles call from ConversationTimeline. It was already computed in AgentDetailTimeline and is now passed as a prop instead of recomputed.
DanielleMaywood
approved these changes
Mar 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Addresses chat page rendering performance. Profiling with React Profiler
showed
AgentChatactual render times of 20–31ms (exceeding the 16ms/60fpsbudget), with
StickyUserMessageas the #1 component bottleneck at 35.7%of self time.
Changes
Hoist
createComponentsto module scope (response.tsx):Previously every
<Response>instance calledcreateComponents()perrender, creating a fresh components map that forced Streamdown to discard
its cached render tree. Now both light/dark variants are precomputed once
at module scope.
Wrap
StickyUserMessageinmemo()(ConversationTimeline.tsx):Profile-confirmed #1 bottleneck. Each instance carries IntersectionObserver
setup.
Wrap
ConversationTimelineinmemo()(ConversationTimeline.tsx):Prevents cascade re-renders from the parent when props haven't changed.
Remove duplicate
buildSubagentTitles(ConversationTimeline.tsx→AgentDetailContent.tsx): Was computed in bothAgentDetailTimelineandConversationTimeline. Now computed once and passed as a prop.Profiling data & analysis
Profiler Metrics
Top Bottleneck Components (self-time %)
Decision log
useMemoforcreateComponentsbecause there are only two possible theme variants and they're static.
make it complex. The memoization fixes should be measured first.
BlockListinmemo()— the React Compiler (enabled forpages/AgentsPage/) already auto-memoizes JSX elements inside it.parseMessagesWithMergedTools)and Phase 3 (radix-ui Tooltip lazy-mounting) deferred to follow-up PRs.