Skip to content

Fix Suspense SSR content hidden for no-JS users (#35460) - #424

Open
everettbu wants to merge 4 commits into
mainfrom
bugfix/issue-35460
Open

Fix Suspense SSR content hidden for no-JS users (#35460)#424
everettbu wants to merge 4 commits into
mainfrom
bugfix/issue-35460

Conversation

@everettbu

Copy link
Copy Markdown

Mirror of facebook/react#35628
Original author: maxathy


Summary

Removed size-based outlining condition that caused large Suspense boundaries to show fallback with a JS instruction to swap content. Without JS, the content never appeared.

This change preserves defer={true} and suspensey content outlining, but forces large content to inline, ensuring visibility for all users.

How did you test this change?

Added new regression test: Created
ReactDOMFizzServerNoScript-test.js
that verifies large Suspense content (>2000 bytes) appears in the initial HTML stream when using a small progressiveChunkSize (100). This confirms no-JS users will see the content instead of the fallback.

Removed size-based outlining condition that caused large Suspense
boundaries to show fallback with a JS instruction to swap content.
Without JS, the content never appeared.

This change preserves defer={true} and suspensey content outlining,
but forces large content to inline, ensuring visibility for all users.
@greptile-apps

greptile-apps Bot commented Jan 26, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds a regression test (ReactDOMFizzServerNoScript-test.js) to verify that large Suspense boundary content is inlined rather than outlined during SSR, ensuring no-JS users see the actual content instead of the fallback. The test renders a 2000-byte string inside <Suspense> with a small progressiveChunkSize: 100 and asserts the fallback ("Loading...") does not appear in the streamed HTML.

  • The test structure is sound: it correctly uses onAllReady + renderToPipeableStream and the critical assertion (not.toContain('Loading...')) accurately distinguishes inlined from outlined boundaries.
  • The first assertion (toContain(largeContent)) passes regardless of whether the boundary is inlined or outlined (outlined boundaries still emit the completed content into the stream), so its inline comment "No-JS user sees it" overstates what the assertion actually verifies.
  • The @jest-environment annotation and module imports match the conventions used in sibling Fizz server tests.

Confidence Score: 2/5

  • Not safe to merge — the PR adds only a test file; the underlying source change it is meant to cover is not present in the diff.
  • The sole changed file is a test. The corresponding fix to packages/react-server/src/ReactFizzServer.js (removing the size-based outlining condition flushedByteSize + boundary.byteSize > request.progressiveChunkSize) does not appear in the PR diff. Without that change, the size check at line 5595–5596 of ReactFizzServer.js remains in place, causing large boundaries to be outlined and the critical assertion expect(streamedContent).not.toContain('Loading...') to fail.
  • packages/react-dom/src/tests/ReactDOMFizzServerNoScript-test.js — test-only change with no accompanying source fix

Important Files Changed

Filename Overview
packages/react-dom/src/tests/ReactDOMFizzServerNoScript-test.js Adds a regression test for no-JS Suspense SSR visibility; test logic is correct but the first assertion's comment misleadingly claims it proves no-JS visibility when it would pass for both inlined and outlined boundaries.

Fix All in Claude Code Fix All in Codex

Prompt To Fix All With AI
This is a comment left during a code review.
Path: packages/react-dom/src/__tests__/ReactDOMFizzServerNoScript-test.js
Line: 55-56

Comment:
**Misleading assertion comment for no-JS visibility**

The comment "No-JS user sees it" is inaccurate. `toContain(largeContent)` passes in **both** the inlined and outlined cases — when a boundary is outlined, the actual content is still written to the stream (as a completed boundary that's swapped in via JavaScript). So this assertion does not distinguish between the two paths and does not prove that no-JS users can see the content.

The true proof of no-JS visibility is the second assertion on line 60 (`not.toContain('Loading...')`). Consider updating the comment to avoid suggesting this assertion is sufficient on its own:

```suggestion
    // Verify large content is present somewhere in the stream
    expect(streamedContent).toContain(largeContent);
```

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: 0197581

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +1 to +62
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment ./scripts/jest/ReactDOMServerIntegrationEnvironment
*/

'use strict';

let React;
let ReactDOMFizzServer;
let Suspense;

describe('ReactDOMFizzServerNoScript', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
Suspense = React.Suspense;
ReactDOMFizzServer = require('react-dom/server');
});

it('renders large content inline for no-JS support (size-based outlining disabled)', async () => {
// Generate content larger than progressiveChunkSize (100)
const largeContent = 'A'.repeat(2000);

function App() {
return (
<Suspense fallback="Loading...">
<div>{largeContent}</div>
</Suspense>
);
}

let streamedContent = '';
const writable = new (require('stream').Writable)({
write(chunk, encoding, callback) {
streamedContent += chunk.toString();
callback();
},
});

await new Promise(resolve => {
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />, {
progressiveChunkSize: 100,
onAllReady() {
pipe(writable);
resolve();
},
});
});

// Verify content is present in the initial stream (No-JS user sees it)
expect(streamedContent).toContain(largeContent);

// Verify fallback is NOT present (because it was inlined, so no pending state)
// Note: If it WAS outlined, we would see "Loading..."
expect(streamedContent).not.toContain('Loading...');
});
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing source changes — test has no corresponding fix

This PR adds a regression test that asserts large Suspense content is inlined rather than outlined, but the actual source change to packages/react-server/src/ReactFizzServer.js is missing from the PR diff. The merge commit (fb79b1e) resolved the merge in a way that dropped all changes to ReactFizzServer.js, while the original commit (29bdf10) did modify it to remove the size-based outlining condition at line 5595:

(flushedByteSize + boundary.byteSize > request.progressiveChunkSize ||
  hasSuspenseyContent(boundary.contentState, flushingShell) ||
  boundary.defer)

Without removing that condition, this test should fail because:

  1. With progressiveChunkSize: 100 and content of 2000 bytes, the boundary will be outlined (emitted as pending with fallback)
  2. The outlined content is still flushed later in the same pass via flushCompletedQueues, so streamedContent will contain both the large content AND "Loading..."
  3. The assertion on line 60 (expect(streamedContent).not.toContain('Loading...')) would fail

The ReactFizzServer.js changes need to be re-applied for this test (and the actual fix) to work.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/react-dom/src/__tests__/ReactDOMFizzServerNoScript-test.js
Line: 1-62

Comment:
**Missing source changes — test has no corresponding fix**

This PR adds a regression test that asserts large Suspense content is inlined rather than outlined, but the actual source change to `packages/react-server/src/ReactFizzServer.js` is **missing from the PR diff**. The merge commit (`fb79b1e`) resolved the merge in a way that dropped all changes to `ReactFizzServer.js`, while the original commit (`29bdf10`) did modify it to remove the size-based outlining condition at line 5595:

```
(flushedByteSize + boundary.byteSize > request.progressiveChunkSize ||
  hasSuspenseyContent(boundary.contentState, flushingShell) ||
  boundary.defer)
```

Without removing that condition, this test should **fail** because:
1. With `progressiveChunkSize: 100` and content of 2000 bytes, the boundary will be **outlined** (emitted as pending with fallback)
2. The outlined content is still flushed later in the same pass via `flushCompletedQueues`, so `streamedContent` will contain both the large content AND `"Loading..."`
3. The assertion on line 60 (`expect(streamedContent).not.toContain('Loading...')`) would fail

The `ReactFizzServer.js` changes need to be re-applied for this test (and the actual fix) to work.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Codex

@everettbu everettbu changed the title [Fizz] Fix Suspense SSR content hidden for no-JS users (#35460) Fix Suspense SSR content hidden for no-JS users (#35460) Mar 16, 2026
Comment on lines +55 to +56
// Verify content is present in the initial stream (No-JS user sees it)
expect(streamedContent).toContain(largeContent);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Misleading assertion comment for no-JS visibility

The comment "No-JS user sees it" is inaccurate. toContain(largeContent) passes in both the inlined and outlined cases — when a boundary is outlined, the actual content is still written to the stream (as a completed boundary that's swapped in via JavaScript). So this assertion does not distinguish between the two paths and does not prove that no-JS users can see the content.

The true proof of no-JS visibility is the second assertion on line 60 (not.toContain('Loading...')). Consider updating the comment to avoid suggesting this assertion is sufficient on its own:

Suggested change
// Verify content is present in the initial stream (No-JS user sees it)
expect(streamedContent).toContain(largeContent);
// Verify large content is present somewhere in the stream
expect(streamedContent).toContain(largeContent);
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/react-dom/src/__tests__/ReactDOMFizzServerNoScript-test.js
Line: 55-56

Comment:
**Misleading assertion comment for no-JS visibility**

The comment "No-JS user sees it" is inaccurate. `toContain(largeContent)` passes in **both** the inlined and outlined cases — when a boundary is outlined, the actual content is still written to the stream (as a completed boundary that's swapped in via JavaScript). So this assertion does not distinguish between the two paths and does not prove that no-JS users can see the content.

The true proof of no-JS visibility is the second assertion on line 60 (`not.toContain('Loading...')`). Consider updating the comment to avoid suggesting this assertion is sufficient on its own:

```suggestion
    // Verify large content is present somewhere in the stream
    expect(streamedContent).toContain(largeContent);
```

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Codex

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants