diff --git a/packages/plugin-rsc/e2e/fixture.ts b/packages/plugin-rsc/e2e/fixture.ts
index 4c927a271..ac1d7385d 100644
--- a/packages/plugin-rsc/e2e/fixture.ts
+++ b/packages/plugin-rsc/e2e/fixture.ts
@@ -217,7 +217,7 @@ export async function setupInlineFixture(options: {
let filepath = path.join(options.dest, filename)
fs.mkdirSync(path.dirname(filepath), { recursive: true })
// strip indent
- contents = contents.replace(/^\n/, '')
+ contents = contents.replace(/^\n*/, '').replace(/\s*$/, '\n')
const indent = contents.match(/^\s*/)?.[0] ?? ''
const strippedContents = contents
.split('\n')
diff --git a/packages/plugin-rsc/e2e/helper.ts b/packages/plugin-rsc/e2e/helper.ts
index b1b167e38..702c5b7ec 100644
--- a/packages/plugin-rsc/e2e/helper.ts
+++ b/packages/plugin-rsc/e2e/helper.ts
@@ -42,3 +42,15 @@ export async function expectNoReload(page: Page) {
},
}
}
+
+export function expectNoPageError(page: Page) {
+ const errors: Error[] = []
+ page.on('pageerror', (error) => {
+ errors.push(error)
+ })
+ return {
+ [Symbol.dispose]: () => {
+ expect(errors).toEqual([])
+ },
+ }
+}
diff --git a/packages/plugin-rsc/e2e/ssr-thenable.test.ts b/packages/plugin-rsc/e2e/ssr-thenable.test.ts
new file mode 100644
index 000000000..7bf9e14e4
--- /dev/null
+++ b/packages/plugin-rsc/e2e/ssr-thenable.test.ts
@@ -0,0 +1,64 @@
+import { test } from '@playwright/test'
+import { setupInlineFixture, type Fixture, useFixture } from './fixture'
+import {
+ expectNoPageError,
+ waitForHydration as waitForHydration_,
+} from './helper'
+
+test.describe(() => {
+ const root = 'examples/e2e/temp/ssr-thenable'
+
+ test.beforeAll(async () => {
+ await setupInlineFixture({
+ src: 'examples/starter',
+ dest: root,
+ files: {
+ 'src/root.tsx': /* tsx */ `
+ import { TestClientUse } from './client.tsx'
+
+ export function Root() {
+ return (
+
+
+
+
+
+
+
+
+ )
+ }
+ `,
+ 'src/client.tsx': /* tsx */ `
+ "use client";
+ import React from 'react'
+
+ const promise = Promise.resolve('ok')
+
+ export function TestClientUse() {
+ const value = React.use(promise)
+ return {value}
+ }
+ `,
+ },
+ })
+ })
+
+ function defineSsrThenableTest(f: Fixture) {
+ test('ssr-thenable', async ({ page }) => {
+ using _ = expectNoPageError(page)
+ await page.goto(f.url())
+ await waitForHydration_(page)
+ })
+ }
+
+ test.describe('dev', () => {
+ const f = useFixture({ root, mode: 'dev' })
+ defineSsrThenableTest(f)
+ })
+
+ test.describe('build', () => {
+ const f = useFixture({ root, mode: 'build' })
+ defineSsrThenableTest(f)
+ })
+})
diff --git a/packages/plugin-rsc/examples/basic/src/framework/entry.ssr.tsx b/packages/plugin-rsc/examples/basic/src/framework/entry.ssr.tsx
index 0fd5e92f3..2c7abbc76 100644
--- a/packages/plugin-rsc/examples/basic/src/framework/entry.ssr.tsx
+++ b/packages/plugin-rsc/examples/basic/src/framework/entry.ssr.tsx
@@ -24,7 +24,11 @@ export async function renderHTML(
// deserialization needs to be kicked off inside ReactDOMServer context
// for ReactDomServer preinit/preloading to work
payload ??= ReactClient.createFromReadableStream(rscStream1)
- return React.use(payload).root
+ return {React.use(payload).root}
+ }
+
+ function FixSsrThenable(props: React.PropsWithChildren) {
+ return props.children
}
// render html (traditional SSR)
diff --git a/packages/plugin-rsc/examples/starter/src/framework/entry.ssr.tsx b/packages/plugin-rsc/examples/starter/src/framework/entry.ssr.tsx
index 1918f834b..8ce5f4e11 100644
--- a/packages/plugin-rsc/examples/starter/src/framework/entry.ssr.tsx
+++ b/packages/plugin-rsc/examples/starter/src/framework/entry.ssr.tsx
@@ -19,12 +19,21 @@ export async function renderHTML(
const [rscStream1, rscStream2] = rscStream.tee()
// deserialize RSC stream back to React VDOM
- let payload: Promise
+ let payload: Promise | undefined
function SsrRoot() {
// deserialization needs to be kicked off inside ReactDOMServer context
// for ReactDomServer preinit/preloading to work
payload ??= ReactClient.createFromReadableStream(rscStream1)
- return React.use(payload).root
+ return {React.use(payload).root}
+ }
+
+ // Add an empty component in between `SsrRoot` and user `root` to avoid React SSR bugs.
+ // SsrRoot (use)
+ // => FixSsrThenable
+ // => root (which potentially has `lazy` + `use`)
+ // https://github.com/facebook/react/issues/33937#issuecomment-3091349011
+ function FixSsrThenable(props: React.PropsWithChildren) {
+ return props.children
}
// render html (traditional SSR)