Files
Wyatt JohnsonandGitHub 5c7daf5aff fix(ppr): ensure fallback route params trigger dynamic resume (#81812)
### What?

This PR fixes an issue where pages with fallback route params weren't
triggering dynamic resume behavior in PPR (Partial Pre-Rendering).

### Why?

When fallback route params exist, the RSC (React Server Components) data
is inherently dynamic because the params are encoded into the flight
router state. Without this fix, pages with fallback route params would
incorrectly be treated as fully static, leading to incorrect behavior.

### How?

- Added a check in `app-render.tsx` to ensure that pages with fallback
route params (`workStore.fallbackRouteParams.size > 0`) always perform a
dynamic resume after the static prerender
- Refactored PPR tests to use deterministic sentinel markers (`<\!--
PPR_BOUNDARY_SENTINEL -->`) instead of time-based measurements for more
reliable testing
- Updated the test utilities to split responses based on the sentinel
marker rather than timing delays

This ensures that:
1. Pages with fallback route params correctly trigger dynamic behavior
2. PPR tests are more reliable and deterministic
3. The boundary between static and dynamic content is clearly marked in
test mode

Fixes #
2025-07-18 17:02:42 -06:00

43 lines
1.5 KiB
TypeScript

/**
* Measures and separates static and dynamic content in a response stream
* by splitting on PPR boundary sentinel markers.
*
* This function is used in Partial Prerendering (PPR) tests to analyze the output
* of Next.js pages and verify that static and dynamic content are properly separated.
*
* @param fn - A function that returns a Promise resolving to a readable stream
* containing HTML with PPR boundary markers
* @returns An object containing the separated static and dynamic chunks
*
* @example
* const [staticPart, dynamicPart] = await splitResponseWithPPRSentinel(async () => {
* return fetch('/some-ppr-page').then(res => res.body)
* })
* console.log(staticPart) // Content before the boundary
* console.log(dynamicPart) // Content after the boundary
*/
export async function splitResponseWithPPRSentinel(
fn: () => Promise<NodeJS.ReadableStream>
): Promise<[staticPart: string, dynamicPart: string]> {
const stream = await fn()
const chunks: string[] = []
await new Promise<void>((resolve, reject) => {
stream.on('data', (chunk: Buffer | string) => {
if (typeof chunk !== 'string') {
chunk = chunk.toString('utf8')
}
chunks.push(chunk)
})
stream.on('end', resolve)
stream.on('error', reject)
})
// Combine all chunks and split on the PPR boundary sentinel
// The sentinel marks the boundary between static and dynamic content
const parts = chunks.join('').split('<!-- PPR_BOUNDARY_SENTINEL -->', 2)
return [parts[0] ?? '', parts[1] ?? '']
}