diff --git a/packages/next/src/build/index.ts b/packages/next/src/build/index.ts index b4e767e01a4c4..d9f6e305b51d1 100644 --- a/packages/next/src/build/index.ts +++ b/packages/next/src/build/index.ts @@ -2771,7 +2771,11 @@ export default async function build( }, // If it's PPR rendered non-static page, bypass the PPR cache when streaming metadata is enabled. // This will skip the postpone data for those bots requests and instead produce a dynamic render. - ...(isRoutePPREnabled && config.experimental.streamingMetadata + ...(isRoutePPREnabled && + // Disable streaming metadata for PPR on deployment where we don't have the special env. + // TODO: enable streaming metadata in PPR mode by default once it's ready. + process.env.__NEXT_EXPERIMENTAL_PPR === 'true' && + config.experimental.streamingMetadata ? [ { type: 'header', diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index 92dc38bbb649e..b1d1ca18c6df3 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -434,6 +434,17 @@ function NonIndex({ ctx }: { ctx: AppRenderContext }) { return null } +function getServeStreamingMetadata(ctx: AppRenderContext) { + const isRoutePPREnabled = !!ctx.renderOpts.experimental.isRoutePPREnabled + const serveStreamingMetadata = !!ctx.renderOpts.serveStreamingMetadata + // If the route is in PPR and the special env is not set, disable the streaming metadata. + // TODO: enable streaming metadata in PPR mode by default once it's ready. + if (isRoutePPREnabled && process.env.__NEXT_EXPERIMENTAL_PPR !== 'true') { + return false + } + return serveStreamingMetadata +} + /** * This is used by server actions & client-side navigations to generate RSC data from a client-side request. * This function is only called on "dynamic" requests (ie, there wasn't already a static response). @@ -473,6 +484,8 @@ async function generateDynamicRSCPayload( url, } = ctx + const serveStreamingMetadata = getServeStreamingMetadata(ctx) + if (!options?.skipFlight) { const preloadCallbacks: PreloadCallbacks = [] @@ -492,7 +505,7 @@ async function generateDynamicRSCPayload( workStore, MetadataBoundary, ViewportBoundary, - serveStreamingMetadata: !!ctx.renderOpts.serveStreamingMetadata, + serveStreamingMetadata, }) const { StreamingMetadata, StaticMetadata } = @@ -501,7 +514,7 @@ async function generateDynamicRSCPayload( // Adding requestId as react key to make metadata remount for each render ) - }, !!ctx.renderOpts.serveStreamingMetadata) + }, serveStreamingMetadata) flightData = ( await walkTreeWithFlightRouterState({ @@ -779,6 +792,7 @@ async function getRSCPayload( getDynamicParamFromSegment, query ) + const serveStreamingMetadata = getServeStreamingMetadata(ctx) const searchParams = createServerSearchParamsForMetadata(query, workStore) const { ViewportTree, MetadataTree, getViewportReady, getMetadataReady } = @@ -797,7 +811,7 @@ async function getRSCPayload( workStore, MetadataBoundary, ViewportBoundary, - serveStreamingMetadata: !!ctx.renderOpts.serveStreamingMetadata, + serveStreamingMetadata: serveStreamingMetadata, }) const preloadCallbacks: PreloadCallbacks = [] @@ -808,7 +822,7 @@ async function getRSCPayload( // Not add requestId as react key to ensure segment prefetch could result consistently if nothing changed ) - }, !!ctx.renderOpts.serveStreamingMetadata) + }, serveStreamingMetadata) const seedData = await createComponentTree({ ctx, @@ -910,6 +924,7 @@ async function getErrorRSCPayload( workStore, } = ctx + const serveStreamingMetadata = getServeStreamingMetadata(ctx) const searchParams = createServerSearchParamsForMetadata(query, workStore) const { MetadataTree, ViewportTree } = createMetadataComponents({ tree, @@ -924,7 +939,7 @@ async function getErrorRSCPayload( workStore, MetadataBoundary, ViewportBoundary, - serveStreamingMetadata: !!ctx.renderOpts.serveStreamingMetadata, + serveStreamingMetadata: serveStreamingMetadata, }) const { StreamingMetadata, StaticMetadata } = @@ -935,7 +950,7 @@ async function getErrorRSCPayload( ), - !!ctx.renderOpts.serveStreamingMetadata + serveStreamingMetadata ) const initialHead = ( diff --git a/test/e2e/app-dir/ppr-metadata-streaming/ppr-metadata-streaming.test.ts b/test/e2e/app-dir/ppr-metadata-streaming/ppr-metadata-streaming.test.ts index fc3d1e28375f5..a05f03a529d1c 100644 --- a/test/e2e/app-dir/ppr-metadata-streaming/ppr-metadata-streaming.test.ts +++ b/test/e2e/app-dir/ppr-metadata-streaming/ppr-metadata-streaming.test.ts @@ -2,6 +2,9 @@ import { nextTestSetup } from 'e2e-utils' import cheerio from 'cheerio' import { assertNoConsoleErrors } from 'next-test-utils' +// TODO: remove this env once streaming metadata is available for ppr +process.env.__NEXT_EXPERIMENTAL_PPR = 'true' + function countSubstring(str: string, substr: string): number { return str.split(substr).length - 1 } diff --git a/test/production/app-dir/metadata-streaming-config/metadata-streaming-config-customized.test.ts b/test/production/app-dir/metadata-streaming-config/metadata-streaming-config-customized.test.ts index 80bb8d81ffe1b..677545a7d38f7 100644 --- a/test/production/app-dir/metadata-streaming-config/metadata-streaming-config-customized.test.ts +++ b/test/production/app-dir/metadata-streaming-config/metadata-streaming-config-customized.test.ts @@ -1,5 +1,8 @@ import { nextTestSetup } from 'e2e-utils' +// TODO: remove this env once streaming metadata is available for ppr +process.env.__NEXT_EXPERIMENTAL_PPR = 'true' + describe('app-dir - metadata-streaming-config-customized', () => { const { next } = nextTestSetup({ files: __dirname, diff --git a/test/production/app-dir/metadata-streaming-config/metadata-streaming-config.test.ts b/test/production/app-dir/metadata-streaming-config/metadata-streaming-config.test.ts index 7805c0493713e..34c868c61ce98 100644 --- a/test/production/app-dir/metadata-streaming-config/metadata-streaming-config.test.ts +++ b/test/production/app-dir/metadata-streaming-config/metadata-streaming-config.test.ts @@ -1,5 +1,8 @@ import { nextTestSetup } from 'e2e-utils' +// TODO: remove this env once streaming metadata is available for ppr +process.env.__NEXT_EXPERIMENTAL_PPR = 'true' + describe('app-dir - metadata-streaming-config', () => { const { next } = nextTestSetup({ files: __dirname, diff --git a/test/production/app-dir/ppr-disable-streaming-metadata/app/dynamic/layout.tsx b/test/production/app-dir/ppr-disable-streaming-metadata/app/dynamic/layout.tsx new file mode 100644 index 0000000000000..a065865841cbc --- /dev/null +++ b/test/production/app-dir/ppr-disable-streaming-metadata/app/dynamic/layout.tsx @@ -0,0 +1,5 @@ +import { Suspense } from 'react' + +export default function Root({ children }) { + return Loading...

}>{children}
+} diff --git a/test/production/app-dir/ppr-disable-streaming-metadata/app/dynamic/page.tsx b/test/production/app-dir/ppr-disable-streaming-metadata/app/dynamic/page.tsx new file mode 100644 index 0000000000000..a74d30071009f --- /dev/null +++ b/test/production/app-dir/ppr-disable-streaming-metadata/app/dynamic/page.tsx @@ -0,0 +1,12 @@ +import { connection } from 'next/server' + +export default async function Page() { + return

hello world

+} + +// Dynamic metadata +export async function generateMetadata() { + await connection() + await new Promise((resolve) => setTimeout(resolve, 1000)) + return { title: 'dynamic-title' } +} diff --git a/test/production/app-dir/ppr-disable-streaming-metadata/app/layout.tsx b/test/production/app-dir/ppr-disable-streaming-metadata/app/layout.tsx new file mode 100644 index 0000000000000..3902c914da24b --- /dev/null +++ b/test/production/app-dir/ppr-disable-streaming-metadata/app/layout.tsx @@ -0,0 +1,16 @@ +import { ReactNode } from 'react' +import Link from 'next/link' + +export default function Root({ children }: { children: ReactNode }) { + return ( + + + + {children} + + + ) +} diff --git a/test/production/app-dir/ppr-disable-streaming-metadata/app/ppr/layout.tsx b/test/production/app-dir/ppr-disable-streaming-metadata/app/ppr/layout.tsx new file mode 100644 index 0000000000000..a065865841cbc --- /dev/null +++ b/test/production/app-dir/ppr-disable-streaming-metadata/app/ppr/layout.tsx @@ -0,0 +1,5 @@ +import { Suspense } from 'react' + +export default function Root({ children }) { + return Loading...

}>{children}
+} diff --git a/test/production/app-dir/ppr-disable-streaming-metadata/app/ppr/page.tsx b/test/production/app-dir/ppr-disable-streaming-metadata/app/ppr/page.tsx new file mode 100644 index 0000000000000..d0385d61a5590 --- /dev/null +++ b/test/production/app-dir/ppr-disable-streaming-metadata/app/ppr/page.tsx @@ -0,0 +1,13 @@ +import { connection } from 'next/server' + +export default async function Page() { + await connection() + return

ppr

+} + +export async function generateMetadata() { + await connection() + return { title: 'ppr-title' } +} + +export const experimental_ppr = true diff --git a/test/production/app-dir/ppr-disable-streaming-metadata/next.config.js b/test/production/app-dir/ppr-disable-streaming-metadata/next.config.js new file mode 100644 index 0000000000000..13d4d0eb7b1e4 --- /dev/null +++ b/test/production/app-dir/ppr-disable-streaming-metadata/next.config.js @@ -0,0 +1,11 @@ +/** + * @type {import('next').NextConfig} + */ +const nextConfig = { + experimental: { + ppr: 'incremental', + streamingMetadata: true, + }, +} + +module.exports = nextConfig