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