From afb2de287d50726c185c767663f021aa016578e5 Mon Sep 17 00:00:00 2001
From: Jiachi Liu
Date: Fri, 14 Feb 2025 00:28:39 +0100
Subject: [PATCH] [metadata] disable streaming metadata for ppr deployment
(#75978)
### What
This PR forcedly disables the streaming metadata on vercel deployment
when PPR is enabled on the target route. There're still some work need
to be done on infra side, so we only let non-PPR mode and self-hosting
PPR (mostly local testing) be controlled by `streamingMetadata` flag.
Once the blocker is gone, then we can re-enable it
---
packages/next/src/build/index.ts | 6 ++++-
.../next/src/server/app-render/app-render.tsx | 27 ++++++++++++++-----
.../ppr-metadata-streaming.test.ts | 3 +++
...tadata-streaming-config-customized.test.ts | 3 +++
.../metadata-streaming-config.test.ts | 3 +++
.../app/dynamic/layout.tsx | 5 ++++
.../app/dynamic/page.tsx | 12 +++++++++
.../app/layout.tsx | 16 +++++++++++
.../app/ppr/layout.tsx | 5 ++++
.../app/ppr/page.tsx | 13 +++++++++
.../next.config.js | 11 ++++++++
11 files changed, 97 insertions(+), 7 deletions(-)
create mode 100644 test/production/app-dir/ppr-disable-streaming-metadata/app/dynamic/layout.tsx
create mode 100644 test/production/app-dir/ppr-disable-streaming-metadata/app/dynamic/page.tsx
create mode 100644 test/production/app-dir/ppr-disable-streaming-metadata/app/layout.tsx
create mode 100644 test/production/app-dir/ppr-disable-streaming-metadata/app/ppr/layout.tsx
create mode 100644 test/production/app-dir/ppr-disable-streaming-metadata/app/ppr/page.tsx
create mode 100644 test/production/app-dir/ppr-disable-streaming-metadata/next.config.js
diff --git a/packages/next/src/build/index.ts b/packages/next/src/build/index.ts
index b4e767e01a4c47..d9f6e305b51d13 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 92dc38bbb649e1..b1d1ca18c6df3d 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 fc3d1e28375f57..a05f03a529d1c4 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 80bb8d81ffe1b4..677545a7d38f7e 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 7805c0493713e7..34c868c61ce988 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 00000000000000..a065865841cbca
--- /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 00000000000000..a74d30071009fe
--- /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 00000000000000..3902c914da24b8
--- /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 00000000000000..a065865841cbca
--- /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 00000000000000..d0385d61a5590b
--- /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 00000000000000..13d4d0eb7b1e4d
--- /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