pulling in latest React#3
Open
balazsbajorics wants to merge 7827 commits intoconcrete-utopia:masterfrom
Open
Conversation
…Document (#34641) It's annoying to have to try to find where it lines up with no hints. This way when you hover over something it should be on screen. The strategy I went with is that it scrolls to a percentage along the scrollable axis but the two might not be exactly the same. Partially because they have different aspect ratios but also because suspended boundaries can shrink the document while the suspense tab needs to still be able to show the boundaries that are currently invisible.
When rects are close together (or overlapping) the outline can end up being covered up by sibling rects or deeper rects. This renders the selected outline on top of everything so it's always visible. <img width="275" height="730" alt="Screenshot 2025-10-29 at 8 43 28 PM" src="https://github.com/user-attachments/assets/69224883-f548-45ec-ada1-1a04ec17eaf5" /> <img width="266" height="737" alt="Screenshot 2025-10-29 at 8 58 53 PM" src="https://github.com/user-attachments/assets/946f7dde-450d-49fd-9fbd-57487f67f461" /> Additionally, this makes it so that it's not part of the translucent tree when things are hidden by the timeline. That way it's easier to see what is selected inside a hidden tree. <img width="498" height="196" alt="Screenshot 2025-10-29 at 8 45 24 PM" src="https://github.com/user-attachments/assets/723107ab-a92c-42c2-8a7d-a548ac3332d0" /> <img width="571" height="735" alt="Screenshot 2025-10-29 at 8 59 06 PM" src="https://github.com/user-attachments/assets/d653f1a7-4096-45c3-b92a-ef155d4742e6" />
…penseList (#35018) We have warned about this for a while now so we can make the switch. Often when you reach for SuspenseList, you mean forwards. It doesn't make sense to have the default to just be a noop. While "together" is another useful mode that's more like a Group so isn't so associated with the default as List. So we're switching it. However, tail=hidden isn't as obvious of a default it does allow for a convenient pattern for streaming in list of items by default. This doesn't yet switch the rendering order of "backwards". That's coming in a follow up.
…rder (#35021) Stacked on #35018. This mounts the children of SuspenseList backwards. Meaning the first child is mounted last in the DOM (and effect list). It's like calling reverse() on the children. This is meant to set us up for allowing AsyncIterable children where the unknown number of children streams in at the end (which is the beginning in a backwards SuspenseList). For consistency we do that with other children too. `unstable_legacy-backwards` still exists for the old mode but is meant to be deprecated. <img width="100" alt="image" src="https://github.com/user-attachments/assets/5c2a95d7-34c4-4a4e-b602-3646a834d779" />
In #35019, we excluded debug I/O info from being considered for enhancing the owner stack if it resolved after the defined `endTime` option that can be passed to the Flight client. However, we should include any I/O that was awaited before that end time, even if it resolved later.
This PR adds a `unstable_reactFragments?: Set<FragmentInstance>` property to DOM nodes that belong to a Fragment with a ref (top level host components). This allows you to access a FragmentInstance from a DOM node. This is flagged behind `enableFragmentRefsInstanceHandles`. The primary use case to unblock is reusing IntersectionObserver instances. A fairly common practice is to cache and reuse IntersectionObservers that share the same config, with a map of node->callbacks to run for each entry in the IO callback. Currently this is not possible with Fragment Ref `observeUsing` because the key in the cache would have to be the `FragmentInstance` and you can't find it without a handle from the node. This works now by accessing `entry.target.fragments`. This also opens up possibilities to use `FragmentInstance` operations in other places, such as events. We can do `event.target.unstable_reactFragments`, then access `fragmentInstance.getClientRects` for example. In a future PR, we can assign an event's `currentTarget` as the Fragment Ref for a more direct handle when the event has been dispatched by the Fragment itself. The first commit here implemented a handle only on observed elements. This is awkward because there isn't a good way to document or expose this temporary property. `element.fragments` is closer to what we would expect from a DOM API if a standard was implemented here. And by assigning it to all top-level nodes of a Fragment, it can be used beyond the cached IntersectionObserver callback. One tradeoff here is adding extra work during the creation of FragmentInstances as well as keeping track of adding/removing nodes. Previously we only track the Fiber on creation but here we add a traversal which could apply to a large set of top-level host children. The `element.unstable_reactFragments` Set can also be randomly ordered.
…5039) For Edge Flight servers, that use Web Streams, we're defining the `debugChannel` option as: ``` debugChannel?: {readable?: ReadableStream, writable?: WritableStream, ...} ``` Whereas for Node.js Flight servers, that use Node.js Streams, we're defining it as: ``` debugChannel?: Readable | Writable | Duplex | WebSocket ``` For the Edge Flight clients, there is currently only one direction of the debug channel supported, so we define the option as: ``` debugChannel?: {readable?: ReadableStream, ...} ``` Consequently, for the Node.js Flight clients, we define the option as: ``` debugChannel?: Readable ``` The presence of a readable debug channel is passed to the Flight client internally via the `hasReadable` flag on the internal `debugChannel` option. For the Node.js clients, that flag was accidentally derived from the public option `debugChannel.readable`, which is conceptually incorrect, because `debugChannel` is a `Readable` stream, not an options object with a `readable` property. However, a `Readable` also has a `readable` property, which is a boolean that indicates whether the stream is in a readable state. This meant that the `hasReadable` flag was incidentally still set correctly. Regardless, this was confusing and unintentional, so we're now fixing it to always set `hasReadable` to `true` when a `debugChannel` is provided to the Node.js clients. We'll revisit this in case we ever add support for writable debug channels in Node.js (and Edge) clients.
We were not recording uEE calls in component/hook syntax. Easy fix. Added tests matching function component syntax for component syntax + added one for hooks
…35042) Normally if you suspend in a SuspenseList row above a Suspense boundary in that row, it'll suspend the parent. Which can itself delay the commit or resuspend a parent boundary. That's because SuspenseList mostly just coordinates the state of the inner boundaries and isn't a boundary itself. However, for tail "hidden" and "collapsed" this is not quite the case because the rows themselves can avoid being rendered. In the case of "collapsed" we require at least one Suspense boundary above to have successfully rendered before committing the list because the idea of this mode is that you should at least always show some indicator that things are still loading. Since we'd never try the next one after that at all, this just works. Expect there was an unrelated bug that meant that "suspend with delay" on a Retry didn't suspend the commit. This caused a scenario were it'd allow a commit proceed when it shouldn't. So I fixed that too. The counter intuitive thing here is that we won't actually show a previous completed row if the loading state of the next row is still loading. For tail "hidden" it's a little different because we don't actually require any loading indicator at all to be shown while it's loading. If we attempt a row and it suspends, we can just hide it (and the rest) and move to commit. Therefore this implements a path where if all the rest of the tail are new mounts (we wouldn't be required to unmount any existing boundaries) then we can treat the SuspenseList boundary itself as "catching" the suspense. This is more coherent semantics since any future row that we didn't attempt also wouldn't resuspend the parent. This allows simple cases like `<SuspenseList>{list}</SuspenseList>` to stream in each row without any indicator and no need for Suspense boundaries.
I don't think we're ready to land this yet since we're using it to run other experiments and our tests. I'm opening this PR to indicate intent to disable and to ensure tests in other combinations still work. Such as enableHalt without enablePostpone. I think we'll also need to rewrite some tests that depend on enablePostpone to preserve some coverage. The conclusion after this experiment is that try/catch around these are too likely to block these signals and consider them error. Throwing works for Hooks and `use()` because the lint rule can ensure that they're not wrapped in try/catch. Throwing in arbitrary functions not quite ecosystem compatible. It's also why there's `use()` and not just throwing a Promise. This might also affect the Catch proposal. The "prerender" for SSR that's supporting "Partial Prerendering" is still there. This just disables the `React.postpone()` API for creating the holes.
We're not shipping this and it's a lot of code to maintain that is blocking my refactor of Fizz for SuspenseList.
…mplement in SSR (#35022) We've long had the CPU suspense feature behind a flag under the terrible API `unstable_expectedLoadTime={arbitraryNumber}`. We've known for a long time we want it to just be `defer={true}` (or just `<Suspense defer>` in the short hand syntax). So this adds the new name and warns for the old name. For only the new name, I also implemented SSR semantics in Fizz. It has two effects here. 1) It renders the fallback before the content (similar to prerender) allowing siblings to complete quicker. 2) It always outlines the result. When streaming this should really happen naturally but if you defer a prerendered content it also implies that it's expensive and should be outlined. It gives you a opt-in to outlining similar to suspensey images and css but let you control it manually.
Follow up to #35022. It's now replaced by the `defer` option. Sounds like nobody is actually using this option, including Meta, so we can just delete it.
This is an alternative to #35059. If the name needs escaping, then instead of escaping it, we just use a base64 name. This wouldn't allow you to match on an escaped name in your own CSS like you should be able to if browsers worked properly. But at least it would provide matching name in current browsers which is probably sufficient if you're using auto-generated names. This also covers some cases where `CSS.escape()` isn't sufficient anyway like when the name ends in a dot.
…35063) Also, don't not skip hidden trees. Memoized state is null when an Offscreen boundary (Suspense or Activity) is visible. This logic was inversed in a couple of View Transition checks which caused pairs to be discovered or not discovered incorrectly for insertion and deletion of Suspense or Activity boundaries.
We already append `randomKey` to each handle name to prevent external libraries from accessing and relying on these internals. But more libraries recently have been getting around this by simply iterating over the element properties and using a `startsWith` check. This flag allows us to experiment with moving these handles to an internal map. This PR starts with the two most common internals, the props object and the fiber. We can consider moving additional properties such as the container root and others depending on perf results.
I need to regain a field because the SuspenseBoundary type is already at 16 fields in prod, after which it deopts v8. There are two fields that are only used in prerender to track postpones. These are ripe to be split into an optional object so that they only take up one field when they're not used.
Stacked on #35067. Same idea of saving a field on the SuspenseBoundary in the common case. The case where they can have a preamble is rare.
## Summary This PR upgrades the dependency on update-notifier, used in react-devtools, to 5.x This is the latest non-ESM version, so upgrading to it should be unproblematic (while updating to 6.x and beyond will have to wait). Upgrading means we avoid installing a lot of outdated dependencies (as can be seen from the diff in yarn.lock), and resolves part of #28058 Changelog: https://github.com/yeoman/update-notifier/releases The most relevant breaking change seems to be that the minimum support node version is increased from v6 to v10, but I couldn't find what is currently React's official node version support. ## How did you test this change? I ran the test-suite locally (`yarn test` in root folder), but I'm not sure if that one actually covers devtools? I also built and tested this version of devtools with some internal company projects (both react and react-native based) – following guidelines from #28058 (comment).
If an error is thrown inside a hidden Activity, it should not escape into the visible part of the UI. Conceptually, a hidden Activity boundary is not part of the current UI; it's the same as an unmounted tree, except for the fact that the state will be restored if it's later revealed. Fixes: - #35073
When I moved the outline to above all other rects, I thought it was clever to unify with the root so that the outline was also used for the root selection. But the root outline is not drawn like the other rects. It's outside the padding and doesn't have the 1px adjustment which leads the overlay to be slightly inside the other rect instead of above it. This goes back to just having the selected root be drawn by the root element. Before: <img width="652" height="253" alt="Screenshot 2025-11-07 at 11 39 28 AM" src="https://github.com/user-attachments/assets/334237d1-f190-4995-94cc-9690ec0f7ce1" /> After: <img width="674" height="220" alt="Screenshot 2025-11-07 at 11 44 01 AM" src="https://github.com/user-attachments/assets/afaa86d8-942a-44d8-a1a5-67c7fb642c0d" />
Small follow up to #35068. Since this is now a single argument we can simplify the creation branching a bit and make sure it's const.
This PR updates the behavior of Activity so that when it is hidden, it hides the contents of any portals contained within it. Previously we had intentionally chosen not to implement this behavior, because it was thought that this concern should be left to the userspace code that manages the portal, e.g. by adding or removing the portal container from the DOM. Depending on the use case for the portal, this is often desirable anyway because the portal container itself is not controlled by React. However, React does own the _contents_ of the portal, and we can hide those elements regardless of what the user chooses to do with the container. This makes the hiding/unhiding behavior of portals with Activity automatic in the majority of cases, and also benefits from aligning the DOM mutations with the rest of the React's commit phase lifecycle. The reason we have to special case this at all is because usually we only hide the direct DOM children of the Activity boundary. There's no reason to go deeper than that, because hiding a parent DOM element effectively hides everything inside of it. Portals are the exception, because they don't exist in the normal DOM hierarchy; we can't assume that just because a portal has a parent in the React tree that it will also have that parent in the actual DOM. So, whenever an Activity boundary is hidden, we must search for and hide _any_ portal that is contained within it, and recursively hide its direct children, too. To optimize this search, we use a new subtree flag, PortalStatic, that is set only on fiber paths that contain a HostPortal. This lets us skip over any subtree that does not contain a portal.
Differential Revision: D86593830 Pull Request resolved: #35085
…eam (#35724) ## Summary - Fixes the `createRequest` call in `renderToPipeableStream` to pass `debugChannelReadable !== undefined` instead of `debugChannel !== undefined` in the turbopack, esm, and parcel Node.js server implementations - The webpack version already had the correct check; this brings the other bundler implementations in line The bug was introduced in #33754. With `debugChannel !== undefined`, the server could signal that debug info should be emitted even when only a write-only debug channel is provided (no readable side), potentially causing the client to block forever waiting for debug data that never arrives.
`encodeReply` throws "React Element cannot be passed to Server Functions from the Client without a temporary reference set" when a React element is the root value of a `serializeModel` call (either passed directly or resolved from a promise), even when a temporary reference set is provided. The cause is that `resolveToJSON` hits the `REACT_ELEMENT_TYPE` switch case before reaching the `existingReference`/`modelRoot` check that regular objects benefit from. The synthetic JSON root created by `JSON.stringify` is never tracked in `writtenObjects`, so `parentReference` is `undefined` and the code falls through to the throw. This adds a `modelRoot` check in the `REACT_ELEMENT_TYPE` case, following the same pattern used for promises and plain objects. The added `JSX as root model` test also uncovered a pre-existing crash in the Flight Client: when the JSX element round-trips back, it arrives as a frozen object (client-created elements are frozen in DEV), and `Object.defineProperty` for `_debugInfo` fails because frozen objects are non-configurable. The same crash can occur with JSX exported as a client reference. For now, we're adding `!Object.isFrozen()` guards in `moveDebugInfoFromChunkToInnerValue` and `addAsyncInfo` to prevent the crash, which means debug info is silently dropped for frozen elements. The proper fix would likely be to clone the element so each rendering context gets its own mutable copy with correct debug info. closes #34984 closes #35690
…35731) When using a partial prerender stream, i.e. a prerender that is intentionally aborted before all I/O has resolved, consumers of `createFromReadableStream` would need to keep the stream unclosed to prevent React Flight from erroring on unresolved chunks. However, some browsers (e.g. Chrome, Firefox) keep unclosed ReadableStreams with pending reads as native GC roots, retaining the entire Flight response. With this PR we're adding an `unstable_allowPartialStream` option, that allows consumers to close the stream normally. The Flight Client's `close()` function then transitions pending chunks to halted instead of erroring them. Halted chunks keep Suspense fallbacks showing (i.e. they never resolve), and their `.then()` is a no-op so no new listeners accumulate. Inner stream chunks (ReadableStream/AsyncIterable) are closed gracefully, and `getChunk()` returns halted chunks for new IDs that are accessed after closing the response. Blocked chunks are left alone because they may be waiting on client-side async operations like module loading, or on forward references to chunks that appeared later in the stream, both of which resolve independently of closing.
…35723) Fixes #33423, #35245, #19732. As demoed [here](#33423 (comment)), React DevTools incorrectly highlights re-renders for descendants of filtered-out nodes that didn't actually render. There were multiple fixes suggesting changes in `didFiberRender()` function, but these doesn't seem right, because this function is used in a context of whether the Fiber actually rendered something (updated), not re-rendered compared to the previous Fiber. Instead, this PR adds additional validation at callsites that either used for highlighting re-renders or capturing tree base durations and are relying on `didFiberRender`. I've also added a few tests that reproduce the failure scenario. Without the changes, the tests are failing.
…filing (#35718) After #34089, when updating (possibly, mounting) inside disconnected subtree, we don't record this as an operation. This only happens during reconnect. The issue is that `recordProfilingDurations()` can be called, which diffs tree base duration and reports it to the Frontend: https://github.com/facebook/react/blob/65db1000b944c8a07b5947c06b38eb8364dce4f2/packages/react-devtools-shared/src/backend/fiber/renderer.js#L4506-L4521 This operation can be recorded before the "Add" operation, and it will not be resolved properly on the Frontend side. Before the fix: ``` commit tree › Suspense › should handle transitioning from fallback back to content during profiling Could not clone the node: commit tree does not contain fiber "5". This is a bug in React DevTools. 162 | const existingNode = nodes.get(id); 163 | if (existingNode == null) { > 164 | throw new Error( | ^ 165 | `Could not clone the node: commit tree does not contain fiber "${id}". This is a bug in React DevTools.`, 166 | ); 167 | } at getClonedNode (packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js:164:13) at updateTree (packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js:348:24) at getCommitTree (packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js:112:20) at ProfilingCache.getCommitTree (packages/react-devtools-shared/src/devtools/ProfilingCache.js:40:46) at Object.<anonymous> (packages/react-devtools-shared/src/__tests__/profilingCommitTreeBuilder-test.js:257:44) ```
…tate (#35740) Co-authored-by: Ruslan Lesiutin <28902667+hoxyq@users.noreply.github.com>
… shared suspenders removed (#35737)
Follow up to #35630 We don't currently have any operations that depend on the updating of text nodes added or removed after Fragment mount. But for the sake of completeness and extending the ability to any other host configs, this change calls `commitNewChildToFragmentInstance` and `deleteChildFromFragmentInstance` on HostText fibers. Both DOM and Fabric configs early return because we cannot attach event listeners or observers to text. In the future, there could be some stateful Fragment feature that uses text that could extend this.
Handles TODOs, small follow up refactors
## Overview While building the RSC sandboxes I notice error messages like: > An error occurred in the `<Offscreen>` component This is an internal component so it should show either: > An error occurred in the `<Suspense>` component. > An error occurred in the `<Activity>` component. It should only happen when there's a lazy in the direct child position of a `<Suspense>` or `<Activity>` component.
…ated code (#35755) This is unused.
…ren before removing (#35775) Currently, this silently removes the last child in the list, which doesn't contain the `id`.
## Summary ESLint v10.0.0 was released on February 7, 2026. The current `peerDependencies` for `eslint-plugin-react-hooks` only allows up to `^9.0.0`, which causes peer dependency warnings when installing with ESLint v10. This PR: - Adds `^10.0.0` to the eslint peer dependency range - Adds `eslint-v10` to devDependencies for testing - Adds an `eslint-v10` e2e fixture (based on the existing `eslint-v9` fixture) ESLint v10's main breaking changes (removal of legacy eslintrc config, deprecated context methods) don't affect this plugin - flat config is already supported since v7.0.0, and the deprecated APIs already have fallbacks in place. ## How did you test this change? Ran the existing unit test suite: ``` cd packages/eslint-plugin-react-hooks && yarn test ``` All 5082 tests passed.
…ion (#35795) When the Flight Client resolves chunk references during model parsing, it calls `transferReferencedDebugInfo` to propagate debug info entries from referenced chunks to the parent chunk. Debug info on chunks is later moved to their resolved values, where it is used by React DevTools to show performance tracks and what a component was suspended by. Debug chunks themselves (specifically `ReactComponentInfo`, `ReactAsyncInfo`, `ReactIOInfo`, and their outlined references) are metadata that is never rendered. They don't need debug info attached to them. Without this fix, debug info entries accumulate on outlined debug chunks via their references to other debug chunks (e.g. owner chains and props deduplication paths). Since each outlined chunk's accumulated entries are copied to every chunk that references it, this creates exponential growth in deep component trees, which can cause the dev server to hang and run out of memory. This generalizes the existing skip of `transferReferencedDebugInfo` for Element owner/stack references (which already recognizes that references to debug chunks don't need debug info transferred) to all references resolved during debug info resolution. It adds an `isInitializingDebugInfo` flag set in `initializeDebugChunk` and `resolveIOInfo`, which propagates through all nested `initializeModelChunk` calls within the same synchronous stack. For the async path, `waitForReference` captures the flag at call time into `InitializationReference.isDebug`, so deferred fulfillments also skip the transfer.
Tracks locations for reactive scope dependencies, both on the deps and portions of the path. The immediate need for this is a non-public experiment where we're exploring type-directed compilation, and sometimes look up the types of expressions by location. We need to preserve locations accurately for that to work, including the locations of the deps. ## Test Plan Locations for dependencies are not easy to test: i manually spot-checked the new fixture to ensure that the deps look right. This is fine as best-effort since it doesn't impact any of our core compilation logic, i may fix forward if there are issues and will think about how to test.
## Summary Add "RCTSelectableText" to the list of component names recognized as being inside a text element, alongside "RCTText". React Native's new text stack, tries to optimize and allows differentiating between a custom TextView, with lower level control, that can reuse the work performed during Fabric/Yoga layout, and a native TextView, used for fidelity. On Android at least, the only place we've needed native TextView for fidelity/native UX has been support for `selectable` text, which has many unique UI interactions. ## How did you test this change? When I patch this in, alongside facebook/react-native#55552, we no longer see warnings when we render text inside of RCTSelectableText component. --------- Co-authored-by: Eli White <github@eli-white.com>
…yload (#35776) ## Summary Follow-up to vercel/next.js#89823 with the actual changes to React. Replaces the `JSON.parse` reviver callback in `initializeModelChunk` with a two-step approach: plain `JSON.parse()` followed by a recursive `reviveModel()` post-process (same as in Flight Reply Server). This yields a **~75% speedup** in RSC chunk deserialization. | Payload | Original (ms) | Walk (ms) | Speedup | |---------|---------------|-----------|---------| | Small (2 elements, 142B) | 0.0024 | 0.0007 | **+72%** | | Medium (~12 elements, 914B) | 0.0116 | 0.0031 | **+73%** | | Large (~90 elements, 16.7KB) | 0.1836 | 0.0451 | **+75%** | | XL (~200 elements, 25.7KB) | 0.3742 | 0.0913 | **+76%** | | Table (1000 rows, 110KB) | 3.0862 | 0.6887 | **+78%** | ## Problem `createFromJSONCallback` returns a reviver function passed as the second argument to `JSON.parse()`. This reviver is called for **every key-value pair** in the parsed JSON. While the logic inside the reviver is lightweight, the dominant cost is the **C++ → JavaScript boundary crossing** — V8's `JSON.parse` is implemented in C++, and calling back into JavaScript for every node incurs significant overhead. Even a trivial no-op reviver `(k, v) => v` makes `JSON.parse` **~4x slower** than bare `JSON.parse` without a reviver: ``` 108 KB payload: Bare JSON.parse: 0.60 ms Trivial reviver: 2.95 ms (+391%) ``` ## Change Replace the reviver with a two-step process: 1. `JSON.parse(resolvedModel)` — parse the entire payload in C++ with no callbacks 2. `reviveModel` — recursively walk the resulting object in pure JavaScript to apply RSC transformations The `reviveModel` function includes additional optimizations over the original reviver: - **Short-circuits plain strings**: only calls `parseModelString` when the string starts with `$`, skipping the vast majority of strings (class names, text content, etc.) - **Stays entirely in JavaScript** — no C++ boundary crossings during the walk ## Results You can find the related applications in the [Next.js PR ](vercel/next.js#89823 I've been testing this on Next.js applications. ### Table as Server Component with 1000 items Before: ``` "min": 13.782875000000786, "max": 22.23400000000038, "avg": 17.116868530000083, "p50": 17.10766700000022, "p75": 18.50787499999933, "p95": 20.426249999998618, "p99": 21.814125000000786 ``` After: ``` "min": 10.963916999999128, "max": 18.096083000000363, "avg": 13.543286884999988, "p50": 13.58350000000064, "p75": 14.871791999999914, "p95": 16.08429099999921, "p99": 17.591458000000785 ``` ### Table as Client Component with 1000 items Before: ``` "min": 3.888875000000553, "max": 9.044959000000745, "avg": 4.651271475000067, "p50": 4.555749999999534, "p75": 4.966624999999112, "p95": 5.47754200000054, "p99": 6.109499999998661 ```` After: ``` "min": 3.5986250000005384, "max": 5.374291000000085, "avg": 4.142990245000046, "p50": 4.10570799999914, "p75": 4.392041999999492, "p95": 4.740084000000934, "p99": 5.1652500000000146 ``` ### Nested Suspense Before: ``` Requests: 200 Min: 73ms Max: 106ms Avg: 78ms P50: 77ms P75: 80ms P95: 85ms P99: 94ms ``` After: ``` Requests: 200 Min: 56ms Max: 67ms Avg: 59ms P50: 58ms P75: 60ms P95: 65ms P99: 66ms ``` ### Even more nested Suspense (double-level Suspense) Before: ``` Requests: 200 Min: 159ms Max: 208ms Avg: 169ms P50: 167ms P75: 173ms P95: 183ms P99: 188ms ``` After: ``` Requests: 200 Min: 125ms Max: 170ms Avg: 134ms P50: 132ms P75: 138ms P95: 148ms P99: 160ms ``` ## How did you test this change? Ran it across many Next.js benchmark applications. The entire Next.js test suite passes with this change. --------- Co-authored-by: Hendrik Liebau <mail@hendrik-liebau.de>
…sh (#35824) When flushing the shell, stylesheets with precedence are emitted in the `<head>` which blocks paint regardless. Outlining a boundary solely because it has suspensey CSS provides no benefit during the shell flush and causes a higher-level fallback to be shown unnecessarily (e.g. "Middle Fallback" instead of "Inner Fallback"). This change passes a flushingInShell flag to hasSuspenseyContent so the host config can skip stylesheet-only suspensey content when flushing the shell. Suspensey images (used for ViewTransition animation reveals) still trigger outlining during the shell since their motivation is different. When flushing streamed completions the behavior is unchanged — suspensey CSS still causes outlining so the parent content can display sooner while the stylesheet loads.
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
No description provided.