diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index a9a22a600dd63..e3ef5cc63ec61 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -611,12 +611,11 @@ async function renderToHTMLOrFlightImpl( const hasPostponed = typeof renderOpts.postponed === 'string' - let stringifiedFlightPayloadPromise = - isStaticGeneration || hasPostponed - ? generateFlight(ctx) - .then((renderResult) => renderResult.toUnchunkedString(true)) - .catch(() => null) - : Promise.resolve(null) + let stringifiedFlightPayloadPromise = isStaticGeneration + ? generateFlight(ctx) + .then((renderResult) => renderResult.toUnchunkedString(true)) + .catch(() => null) + : Promise.resolve(null) // Get the nonce from the incoming request if it has one. const csp = req.headers['content-security-policy'] diff --git a/test/production/standalone-mode/required-server-files/app/delayed/page.js b/test/production/standalone-mode/required-server-files/app/delayed/page.js new file mode 100644 index 0000000000000..04d1f9ec01106 --- /dev/null +++ b/test/production/standalone-mode/required-server-files/app/delayed/page.js @@ -0,0 +1,30 @@ +import { Suspense } from 'react' +import { cookies } from 'next/headers' + +export default function Page() { + return ( + <> +

delayed page

+ + + + + + + + + ) +} + +async function Time() { + cookies() + await new Promise((resolve) => setTimeout(resolve, 1000)) + return

{Date.now()}

+} + +async function Random() { + cookies() + await new Promise((resolve) => setTimeout(resolve, 4000)) + return

{Math.random()}

+} diff --git a/test/production/standalone-mode/required-server-files/required-server-files-ppr.test.ts b/test/production/standalone-mode/required-server-files/required-server-files-ppr.test.ts index 482f1966d26cb..6bf49e7eb9676 100644 --- a/test/production/standalone-mode/required-server-files/required-server-files-ppr.test.ts +++ b/test/production/standalone-mode/required-server-files/required-server-files-ppr.test.ts @@ -15,6 +15,7 @@ describe('required server files app router', () => { let next: NextInstance let server let appPort + let delayedPostpone const setupNext = async ({ nextEnv, @@ -50,6 +51,9 @@ describe('required server files app router', () => { }) await next.stop() + delayedPostpone = (await next.readJSON('.next/server/app/delayed.meta')) + .postponed + await fs.move( join(next.testDir, '.next/standalone'), join(next.testDir, 'standalone') @@ -106,6 +110,37 @@ describe('required server files app router', () => { expect(next.cliOutput).not.toContain('ERR_INVALID_URL') }) + it('should properly stream resume', async () => { + const res = await fetchViaHTTP(appPort, '/delayed', undefined, { + headers: { + 'x-matched-path': '/_next/postponed/resume/delayed', + }, + method: 'POST', + body: delayedPostpone, + }) + + let chunks = [] + + for await (const chunk of res.body) { + chunks.push({ + time: Date.now(), + chunk: chunk.toString(), + }) + } + + const firstSuspense = chunks.find((item) => item.chunk.includes('time')) + const secondSuspense = chunks.find((item) => item.chunk.includes('random')) + + console.log({ + firstSuspense, + secondSuspense, + }) + + expect(secondSuspense.time - firstSuspense.time).toBeGreaterThanOrEqual( + 2 * 1000 + ) + }) + it('should properly handle prerender for bot request', async () => { const res = await fetchViaHTTP(appPort, '/isr/first', undefined, { headers: {