perf: cache repeated React elements in component tree creation#91618
perf: cache repeated React elements in component tree creation#91618benfavre wants to merge 1 commit intovercel:canaryfrom
Conversation
Three optimizations to reduce per-request overhead in the dynamic render path: 1. **Cache RenderFromTemplateContext element across requests**: The element `createElement(RenderFromTemplateContext, null)` is always identical (no props, no children, same component reference). A module-level WeakMap keyed on the component reference caches this element so it is created once and reused across all segments and requests. React elements are immutable value objects, so sharing references is safe and gives React Flight fewer unique elements to serialize. 2. **Hoist loop-invariant computations out of the parallel routes loop**: `templateNode`, `templateFilePath`, `errorFilePath`, `loadingFilePath`, `globalErrorFilePath`, `wrappedErrorStyles`, `segmentViewBoundaries`, and `resolvedTemplateForRouter` all depend only on the current segment (tree, dir, Template, etc.) — not on `parallelRouteKey`. Moving them before the `Promise.all(Object.keys(parallelRoutes).map(...))` avoids redundant `getConventionPathByType` lookups and `createElement` calls for every parallel route slot. This also eliminates a duplicate `loadingFilePath` computation that was repeated after the loop. 3. **Fast-path getLayerAssets when no layout/page path exists**: When a segment has no layout or page file (`layoutOrPagePath` is undefined), `getLayerAssets` was still calling `getLinkAndScriptTags` and `renderCssResource` with empty arrays before returning null. An early return skips all manifest lookups and array allocations. For a 10-layout deep dynamic route, this saves ~10 redundant `getConventionPathByType` calls (×4 conventions = ~40 lookups), ~10 `createElement(RenderFromTemplateContext, null)` allocations, and the associated `getLayerAssets` work for segments without CSS/JS. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Allow CI Workflow Run
Note: this should only be enabled once the PR is ready to go and can only be enabled by a maintainer |
Performance ImpactProfiling setup: Node.js v25.7.0, Context: React rendering takes 813μs/req (44% CPU) for the dynamic route. Each of the 10 segments creates identical React elements ( Changes:
These save ~10 Test Verification
|
Regression SafetyZero regression risk. React elements are immutable value objects — the spec explicitly allows reusing references. Flight serialization handles shared references via module reference deduplication. Loop-invariant hoisting moves computations before the loop without changing their output. Benchmark
Saves ~10 createElement calls + ~10 object destructurings + eliminates duplicate Test Verification
|
Summary
Reduces per-request overhead in the dynamic render path by eliminating redundant React element creation and manifest lookups in
createComponentTreeInternal.Cache
RenderFromTemplateContextelement across requests via a module-levelWeakMapkeyed on the component reference. The elementcreateElement(RenderFromTemplateContext, null)is always identical (no props, no children), so the cached reference is reused across all segments and requests. React elements are immutable value objects, making this safe. Gives React Flight fewer unique elements to serialize.Hoist loop-invariant computations out of the parallel routes loop:
templateNode,templateFilePath,errorFilePath,loadingFilePath,globalErrorFilePath,wrappedErrorStyles,segmentViewBoundaries, andresolvedTemplateForRouterdepend only on the current segment — not onparallelRouteKey. Moving them before thePromise.allloop avoids redundantgetConventionPathByTypelookups andcreateElementcalls per slot. Also eliminates a duplicateloadingFilePathcomputation after the loop.Fast-path
getLayerAssetswhen no layout/page path exists: Early return whenlayoutOrPagePathisundefinedskipsgetLinkAndScriptTags,getPreloadableFonts, andrenderCssResourcecalls that would return empty results anyway.For a 10-layout deep dynamic route, this saves ~40 redundant
getConventionPathByTypelookups, ~10createElement(RenderFromTemplateContext, null)allocations, and associatedgetLayerAssetswork for segments without CSS/JS.Test plan
test/e2e/app-dir/parallel-routes-*)cache-components-create-component-treetest passes🤖 Generated with Claude Code