diff --git a/.changeset/green-bananas-guess.md b/.changeset/green-bananas-guess.md
new file mode 100644
index 000000000..314f032b0
--- /dev/null
+++ b/.changeset/green-bananas-guess.md
@@ -0,0 +1,5 @@
+---
+'@vercel/next': patch
+---
+
+Fixed headers for static routes when PPR is enabled
diff --git a/packages/next/src/utils.ts b/packages/next/src/utils.ts
index ca0c6daca..0604d50ff 100644
--- a/packages/next/src/utils.ts
+++ b/packages/next/src/utils.ts
@@ -2429,7 +2429,8 @@ export const onPrerenderRoute =
initialHeaders: {
'content-type': rscContentTypeHeader,
vary: rscVaryHeader,
- ...(experimentalPPR && rscDidPostponeHeader
+ // If it contains a pre-render, then it was postponed.
+ ...(prerender && rscDidPostponeHeader
? { [rscDidPostponeHeader]: '1' }
: {}),
},
diff --git a/packages/next/test/fixtures/00-app-dir-ppr-full/app/dynamic/force-dynamic/page.jsx b/packages/next/test/fixtures/00-app-dir-ppr-full/app/dynamic/force-dynamic/page.jsx
new file mode 100644
index 000000000..c5105deaa
--- /dev/null
+++ b/packages/next/test/fixtures/00-app-dir-ppr-full/app/dynamic/force-dynamic/page.jsx
@@ -0,0 +1,13 @@
+import React, { Suspense } from 'react'
+import { Dynamic } from '../../../components/dynamic'
+
+export const dynamic = 'force-dynamic'
+export const revalidate = 60
+
+export default ({ params: { slug } }) => {
+ return (
+ }>
+
+
+ )
+}
diff --git a/packages/next/test/fixtures/00-app-dir-ppr-full/app/dynamic/force-static/page.jsx b/packages/next/test/fixtures/00-app-dir-ppr-full/app/dynamic/force-static/page.jsx
new file mode 100644
index 000000000..8ceabc2c1
--- /dev/null
+++ b/packages/next/test/fixtures/00-app-dir-ppr-full/app/dynamic/force-static/page.jsx
@@ -0,0 +1,13 @@
+import React, { Suspense } from 'react'
+import { Dynamic } from '../../../components/dynamic'
+
+export const dynamic = 'force-static'
+export const revalidate = 60
+
+export default ({ params: { slug } }) => {
+ return (
+ }>
+
+
+ )
+}
diff --git a/packages/next/test/fixtures/00-app-dir-ppr-full/app/layout.jsx b/packages/next/test/fixtures/00-app-dir-ppr-full/app/layout.jsx
new file mode 100644
index 000000000..a40f10ae1
--- /dev/null
+++ b/packages/next/test/fixtures/00-app-dir-ppr-full/app/layout.jsx
@@ -0,0 +1,43 @@
+import React from 'react'
+import Link from 'next/link'
+
+const links = [
+ { href: '/', tag: 'pre-generated' },
+ { href: '/nested/a', tag: 'pre-generated' },
+ { href: '/nested/b', tag: 'on-demand' },
+ { href: '/nested/c', tag: 'on-demand' },
+ { href: '/on-demand/a', tag: 'on-demand, no-gsp' },
+ { href: '/on-demand/b', tag: 'on-demand, no-gsp' },
+ { href: '/on-demand/c', tag: 'on-demand, no-gsp' },
+ { href: '/static', tag: 'static' },
+ { href: '/no-suspense', tag: 'no suspense' },
+ { href: '/no-suspense/nested/a', tag: 'no suspense, pre-generated' },
+ { href: '/no-suspense/nested/b', tag: 'no suspense, on-demand' },
+ { href: '/no-suspense/nested/c', tag: 'no suspense, on-demand' },
+ { href: '/dynamic/force-dynamic', tag: "dynamic = 'force-dynamic'" },
+ { href: '/dynamic/force-static', tag: "dynamic = 'force-static'" },
+]
+
+export default ({ children }) => {
+ return (
+
+
+ Partial Prerendering
+
+ Below are links that are associated with different pages that all will
+ partially prerender
+
+
+ {children}
+
+
+ )
+}
diff --git a/packages/next/test/fixtures/00-app-dir-ppr-full/app/nested/[slug]/page.jsx b/packages/next/test/fixtures/00-app-dir-ppr-full/app/nested/[slug]/page.jsx
new file mode 100644
index 000000000..fec0c0923
--- /dev/null
+++ b/packages/next/test/fixtures/00-app-dir-ppr-full/app/nested/[slug]/page.jsx
@@ -0,0 +1,16 @@
+import React, { Suspense } from 'react'
+import { Dynamic } from '../../../components/dynamic'
+
+export const revalidate = 60
+
+export default ({ params: { slug } }) => {
+ return (
+ }>
+
+
+ )
+}
+
+export const generateStaticParams = async () => {
+ return [{ slug: 'a' }]
+}
diff --git a/packages/next/test/fixtures/00-app-dir-ppr-full/app/no-suspense/nested/[slug]/page.jsx b/packages/next/test/fixtures/00-app-dir-ppr-full/app/no-suspense/nested/[slug]/page.jsx
new file mode 100644
index 000000000..d7f48f3a2
--- /dev/null
+++ b/packages/next/test/fixtures/00-app-dir-ppr-full/app/no-suspense/nested/[slug]/page.jsx
@@ -0,0 +1,10 @@
+import React from 'react'
+import { Dynamic } from '../../../../components/dynamic'
+
+export default ({ params: { slug } }) => {
+ return
+}
+
+export const generateStaticParams = async () => {
+ return [{ slug: 'a' }]
+}
diff --git a/packages/next/test/fixtures/00-app-dir-ppr-full/app/no-suspense/page.jsx b/packages/next/test/fixtures/00-app-dir-ppr-full/app/no-suspense/page.jsx
new file mode 100644
index 000000000..ef2cf10a3
--- /dev/null
+++ b/packages/next/test/fixtures/00-app-dir-ppr-full/app/no-suspense/page.jsx
@@ -0,0 +1,6 @@
+import React from 'react'
+import { Dynamic } from '../../components/dynamic'
+
+export default () => {
+ return
+}
diff --git a/packages/next/test/fixtures/00-app-dir-ppr-full/app/on-demand/[slug]/page.jsx b/packages/next/test/fixtures/00-app-dir-ppr-full/app/on-demand/[slug]/page.jsx
new file mode 100644
index 000000000..289d8a9d6
--- /dev/null
+++ b/packages/next/test/fixtures/00-app-dir-ppr-full/app/on-demand/[slug]/page.jsx
@@ -0,0 +1,10 @@
+import React, { Suspense } from 'react'
+import { Dynamic } from '../../../components/dynamic'
+
+export default ({ params: { slug } }) => {
+ return (
+ }>
+
+
+ )
+}
diff --git a/packages/next/test/fixtures/00-app-dir-ppr-full/app/page.jsx b/packages/next/test/fixtures/00-app-dir-ppr-full/app/page.jsx
new file mode 100644
index 000000000..1d5918209
--- /dev/null
+++ b/packages/next/test/fixtures/00-app-dir-ppr-full/app/page.jsx
@@ -0,0 +1,10 @@
+import React, { Suspense } from 'react'
+import { Dynamic } from '../components/dynamic'
+
+export default () => {
+ return (
+ }>
+
+
+ )
+}
diff --git a/packages/next/test/fixtures/00-app-dir-ppr-full/app/static/page.jsx b/packages/next/test/fixtures/00-app-dir-ppr-full/app/static/page.jsx
new file mode 100644
index 000000000..f90cd84e7
--- /dev/null
+++ b/packages/next/test/fixtures/00-app-dir-ppr-full/app/static/page.jsx
@@ -0,0 +1,6 @@
+import React from 'react'
+import { Dynamic } from '../../components/dynamic'
+
+export default () => {
+ return
+}
diff --git a/packages/next/test/fixtures/00-app-dir-ppr-full/components/dynamic.jsx b/packages/next/test/fixtures/00-app-dir-ppr-full/components/dynamic.jsx
new file mode 100644
index 000000000..65d7f95f1
--- /dev/null
+++ b/packages/next/test/fixtures/00-app-dir-ppr-full/components/dynamic.jsx
@@ -0,0 +1,37 @@
+import React from 'react'
+import { headers } from 'next/headers'
+
+export const Dynamic = ({ pathname, fallback }) => {
+ if (fallback) {
+ return Loading...
+ }
+
+ const messages = []
+ const names = ['x-test-input', 'user-agent']
+ const list = headers()
+
+ for (const name of names) {
+ messages.push({ name, value: list.get(name) })
+ }
+
+ return (
+
+
+ {pathname && (
+ <>
+ - Pathname
+ - {pathname}
+ >
+ )}
+ {messages.map(({ name, value }) => (
+
+ -
+ Header:
{name}
+
+ - {value ?? 'null'}
+
+ ))}
+
+
+ )
+}
diff --git a/packages/next/test/fixtures/00-app-dir-ppr-full/index.test.js b/packages/next/test/fixtures/00-app-dir-ppr-full/index.test.js
new file mode 100644
index 000000000..8a22c3a7f
--- /dev/null
+++ b/packages/next/test/fixtures/00-app-dir-ppr-full/index.test.js
@@ -0,0 +1,103 @@
+/* eslint-env jest */
+const path = require('path');
+const { deployAndTest } = require('../../utils');
+const fetch = require('../../../../../test/lib/deployment/fetch-retry');
+
+const pages = [
+ { pathname: '/', dynamic: true },
+ { pathname: '/nested/a', dynamic: true },
+ { pathname: '/nested/b', dynamic: true },
+ { pathname: '/nested/c', dynamic: true },
+ { pathname: '/on-demand/a', dynamic: true },
+ { pathname: '/on-demand/b', dynamic: true },
+ { pathname: '/on-demand/c', dynamic: true },
+ { pathname: '/static', dynamic: false },
+ { pathname: '/no-suspense', dynamic: true },
+ { pathname: '/no-suspense/nested/a', dynamic: true },
+ { pathname: '/no-suspense/nested/b', dynamic: true },
+ { pathname: '/no-suspense/nested/c', dynamic: true },
+ // TODO: uncomment when we've fixed the 404 case for force-dynamic pages
+ // { pathname: '/dynamic/force-dynamic', dynamic: 'force-dynamic' },
+ { pathname: '/dynamic/force-static', dynamic: false },
+];
+
+const ctx = {};
+
+describe(`${__dirname.split(path.sep).pop()}`, () => {
+ beforeAll(async () => {
+ const info = await deployAndTest(__dirname);
+ Object.assign(ctx, info);
+ });
+
+ describe('dynamic pages should resume', () => {
+ it.each(pages.filter(p => p.dynamic))(
+ 'should resume $pathname',
+ async ({ pathname }) => {
+ const expected = `${Date.now()}:${Math.random()}`;
+ const res = await fetch(`${ctx.deploymentUrl}${pathname}`, {
+ headers: { 'X-Test-Input': expected },
+ });
+ expect(res.status).toEqual(200);
+ expect(res.headers.get('content-type')).toEqual(
+ 'text/html; charset=utf-8'
+ );
+ const html = await res.text();
+ expect(html).toContain(expected);
+ expect(html).toContain('