From c215811e4d30c9d2ea48c9f3b5d8ea52f5ef7cc2 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Tue, 11 Feb 2025 01:01:11 +0100 Subject: [PATCH 1/7] do not throw postpone error when streaming metadata is not enabled --- packages/next/src/lib/metadata/metadata.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/next/src/lib/metadata/metadata.tsx b/packages/next/src/lib/metadata/metadata.tsx index ff5ac93e5af74..96d847179c3b0 100644 --- a/packages/next/src/lib/metadata/metadata.tsx +++ b/packages/next/src/lib/metadata/metadata.tsx @@ -162,14 +162,14 @@ export function createMetadataComponents({ } catch (notFoundMetadataErr) { // In PPR rendering we still need to throw the postpone error. // If metadata is postponed, React needs to be aware of the location of error. - if (isPostpone(notFoundMetadataErr)) { + if (serveStreamingMetadata && isPostpone(notFoundMetadataErr)) { throw notFoundMetadataErr } } } // In PPR rendering we still need to throw the postpone error. // If metadata is postponed, React needs to be aware of the location of error. - if (isPostpone(metadataErr)) { + if (serveStreamingMetadata && isPostpone(metadataErr)) { throw metadataErr } // We don't actually want to error in this component. We will From 21a09be95e716d8c916cc3f518c275656e2813e3 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Tue, 11 Feb 2025 01:25:34 +0100 Subject: [PATCH 2/7] update test --- .../metadata-static-generation.test.ts | 72 ++++++++++--------- ...ppr-metadata-blocking-ppr-fallback.test.ts | 34 +++++++++ .../ppr-metadata-blocking.test.ts | 13 ++-- 3 files changed, 80 insertions(+), 39 deletions(-) create mode 100644 test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking-ppr-fallback.test.ts diff --git a/test/e2e/app-dir/metadata-static-generation/metadata-static-generation.test.ts b/test/e2e/app-dir/metadata-static-generation/metadata-static-generation.test.ts index 85af160aa6d77..d8c0dd6528921 100644 --- a/test/e2e/app-dir/metadata-static-generation/metadata-static-generation.test.ts +++ b/test/e2e/app-dir/metadata-static-generation/metadata-static-generation.test.ts @@ -2,42 +2,46 @@ import { nextTestSetup } from 'e2e-utils' const isPPREnabled = process.env.__NEXT_EXPERIMENTAL_PPR === 'true' -describe('app-dir - metadata-static-generation', () => { - const { next, isNextStart } = nextTestSetup({ - files: __dirname, - }) +// PPR tests are covered in test/e2e/app-dir/ppr-metadata-blocking +;(isPPREnabled ? describe.skip : describe)( + 'app-dir - metadata-static-generation', + () => { + const { next, isNextStart } = nextTestSetup({ + files: __dirname, + }) + + if (isNextStart) { + // Precondition for the following tests in build mode. + // This test is only useful for non-PPR mode as in PPR mode those routes + // are all listed in the prerender manifest. + it('should generate all pages static', async () => { + const prerenderManifest = JSON.parse( + await next.readFile('.next/prerender-manifest.json') + ) + const staticRoutes = prerenderManifest.routes + expect(Object.keys(staticRoutes).sort()).toEqual([ + '/', + '/suspenseful/static', + ]) + }) + } - if (isNextStart && !isPPREnabled) { - // Precondition for the following tests in build mode. - // This test is only useful for non-PPR mode as in PPR mode those routes - // are all listed in the prerender manifest. - it('should generate all pages static', async () => { - const prerenderManifest = JSON.parse( - await next.readFile('.next/prerender-manifest.json') + it('should contain async generated metadata in head for simple static page', async () => { + const $ = await next.render$('/') + expect($('head title').text()).toBe('index page') + expect($('head meta[name="description"]').attr('content')).toBe( + 'index page description' ) - const staticRoutes = prerenderManifest.routes - expect(Object.keys(staticRoutes).sort()).toEqual([ - '/', - '/suspenseful/static', - ]) }) - } - - it('should contain async generated metadata in head for simple static page', async () => { - const $ = await next.render$('/') - expect($('head title').text()).toBe('index page') - expect($('head meta[name="description"]').attr('content')).toBe( - 'index page description' - ) - }) - it('should contain async generated metadata in head static page with suspenseful content', async () => { - const $ = await next.render$('/suspenseful/static') - expect($('head title').text()).toBe('suspenseful page - static') - }) + it('should contain async generated metadata in head static page with suspenseful content', async () => { + const $ = await next.render$('/suspenseful/static') + expect($('head title').text()).toBe('suspenseful page - static') + }) - it('should contain async generated metadata in head for dynamic page', async () => { - const $ = await next.render$('/suspenseful/dynamic') - expect($('head title').text()).toBe('suspenseful page - dynamic') - }) -}) + it('should contain async generated metadata in head for dynamic page', async () => { + const $ = await next.render$('/suspenseful/dynamic') + expect($('head title').text()).toBe('suspenseful page - dynamic') + }) + } +) diff --git a/test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking-ppr-fallback.test.ts b/test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking-ppr-fallback.test.ts new file mode 100644 index 0000000000000..3dbb119f53c8e --- /dev/null +++ b/test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking-ppr-fallback.test.ts @@ -0,0 +1,34 @@ +import { nextTestSetup } from 'e2e-utils' + +function countSubstring(str: string, substr: string): number { + return str.split(substr).length - 1 +} + +describe('ppr-metadata-blocking-ppr-fallback', () => { + const { next } = nextTestSetup({ + files: __dirname, + env: { + __NEXT_EXPERIMENTAL_STATIC_SHELL_DEBUGGING: '1', + }, + }) + + it('should not include metadata in partial shell when page is fully dynamic', async () => { + const $ = await next.render$('/fully-dynamic?__nextppronly=fallback') + expect(countSubstring($.html(), '')).toBe(0) + }) + + it('should include viewport metadata in partial shell when metadata is dynamic under suspense', async () => { + const $ = await next.render$( + '/dynamic-metadata/partial?__nextppronly=fallback' + ) + expect(countSubstring($.html(), '<title>')).toBe(0) + expect(countSubstring($.html(), '<meta name="viewport"')).toBe(1) + }) + + it('should include viewport metadata in partial shell when page is partially dynamic', async () => { + const $ = await next.render$('/dynamic-page/partial?__nextppronly=fallback') + expect($('head title').text()).toBe('dynamic-page - partial') + expect(countSubstring($.html(), '<title>')).toBe(1) + expect(countSubstring($.html(), '<meta name="viewport"')).toBe(1) + }) +}) diff --git a/test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking.test.ts b/test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking.test.ts index 7ce60bc583f0c..2777958e2c9f0 100644 --- a/test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking.test.ts +++ b/test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking.test.ts @@ -55,8 +55,8 @@ describe('ppr-metadata-blocking', () => { it('should generate metadata in head when page content is static', async () => { const $ = await next.render$('/dynamic-metadata') - expect($('head title').text()).toBe('dynamic metadata') expect(countSubstring($.html(), '<title>')).toBe(1) + expect($('head title').text()).toBe('dynamic metadata') const browser = await next.browser('/dynamic-metadata') expect(await browser.waitForElementByCss('head title').text()).toBe( @@ -69,8 +69,8 @@ describe('ppr-metadata-blocking', () => { describe('partial shell', () => { it('should insert metadata into head with dynamic metadata and wrapped under layout Suspense boundary', async () => { const $ = await next.render$('/dynamic-metadata/partial') - expect($('head title').text()).toBe('dynamic-metadata - partial') - expect(countSubstring($.html(), '<title>')).toBe(1) + expect(countSubstring($.html(), '<title>')).toBe(0) + // expect($('head title').text()).toBe('dynamic-metadata - partial') const browser = await next.browser('/dynamic-metadata/partial') expect(await browser.waitForElementByCss('head title').text()).toBe( @@ -133,7 +133,10 @@ describe('ppr-metadata-blocking', () => { // Dynamic render should not have postponed header const headers = res1.headers - expect(headers.get('x-nextjs-postponed')).toBe(null) + // In blocking mode of metadata, it's still postponed if metadata or page is dynamic. + // It won't behave differently when the bot is visiting. + + expect(headers.get('x-nextjs-postponed')).toBe('1') const $1 = cheerio.load(await res1.text()) const $2 = cheerio.load(await res2.text()) @@ -142,7 +145,7 @@ describe('ppr-metadata-blocking', () => { const attribute2 = parseInt($2('[data-date]').attr('data-date')) // Two requests are dynamic and should not have the same data-date attribute - expect(attribute2).toBeGreaterThan(attribute1) + expect(attribute2).toEqual(attribute1) expect(attribute1).toBeTruthy() }) }) From 0492da0650c7e8c6f831ceead6106a041c53708a Mon Sep 17 00:00:00 2001 From: Jiachi Liu <inbox@huozhi.im> Date: Tue, 11 Feb 2025 01:38:39 +0100 Subject: [PATCH 3/7] fix test --- .../ppr-metadata-blocking.test.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking.test.ts b/test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking.test.ts index 2777958e2c9f0..3c16e43b9e58e 100644 --- a/test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking.test.ts +++ b/test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking.test.ts @@ -7,7 +7,7 @@ function countSubstring(str: string, substr: string): number { } describe('ppr-metadata-blocking', () => { - const { next, isNextStart } = nextTestSetup({ + const { next, isNextDev, isNextStart } = nextTestSetup({ files: __dirname, }) @@ -69,8 +69,14 @@ describe('ppr-metadata-blocking', () => { describe('partial shell', () => { it('should insert metadata into head with dynamic metadata and wrapped under layout Suspense boundary', async () => { const $ = await next.render$('/dynamic-metadata/partial') - expect(countSubstring($.html(), '<title>')).toBe(0) - // expect($('head title').text()).toBe('dynamic-metadata - partial') + // Dev: dynamic rendering + if (isNextDev) { + expect(countSubstring($.html(), '<title>')).toBe(1) + expect($('head title').text()).toBe('dynamic-metadata - partial') + } else { + // Production: PPR + expect(countSubstring($.html(), '<title>')).toBe(0) + } const browser = await next.browser('/dynamic-metadata/partial') expect(await browser.waitForElementByCss('head title').text()).toBe( From 05778760b0abd944840c406abc46fcf4737994c0 Mon Sep 17 00:00:00 2001 From: Jiachi Liu <inbox@huozhi.im> Date: Tue, 11 Feb 2025 02:04:53 +0100 Subject: [PATCH 4/7] fix test --- .../app-dir/ppr-metadata-blocking/ppr-metadata-blocking.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking.test.ts b/test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking.test.ts index 3c16e43b9e58e..0a4664744a795 100644 --- a/test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking.test.ts +++ b/test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking.test.ts @@ -142,7 +142,7 @@ describe('ppr-metadata-blocking', () => { // In blocking mode of metadata, it's still postponed if metadata or page is dynamic. // It won't behave differently when the bot is visiting. - expect(headers.get('x-nextjs-postponed')).toBe('1') + expect(headers.get('x-nextjs-postponed')).toBe(null) const $1 = cheerio.load(await res1.text()) const $2 = cheerio.load(await res2.text()) From b173187f4d79adc6c62473c7bc014452bb2dc141 Mon Sep 17 00:00:00 2001 From: Jiachi Liu <inbox@huozhi.im> Date: Tue, 11 Feb 2025 02:11:57 +0100 Subject: [PATCH 5/7] fix test --- .../app-dir/ppr-metadata-blocking/ppr-metadata-blocking.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking.test.ts b/test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking.test.ts index 0a4664744a795..20dbf3655bc96 100644 --- a/test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking.test.ts +++ b/test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking.test.ts @@ -151,7 +151,7 @@ describe('ppr-metadata-blocking', () => { const attribute2 = parseInt($2('[data-date]').attr('data-date')) // Two requests are dynamic and should not have the same data-date attribute - expect(attribute2).toEqual(attribute1) + expect(attribute2).not.toEqual(attribute1) expect(attribute1).toBeTruthy() }) }) From b8250a3dc84c4bdb466067934b6d5963d048bb3b Mon Sep 17 00:00:00 2001 From: Jiachi Liu <inbox@huozhi.im> Date: Tue, 11 Feb 2025 02:32:43 +0100 Subject: [PATCH 6/7] skip test --- .../ppr-metadata-blocking/ppr-metadata-blocking.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking.test.ts b/test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking.test.ts index 20dbf3655bc96..e26899bda5c50 100644 --- a/test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking.test.ts +++ b/test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking.test.ts @@ -7,9 +7,11 @@ function countSubstring(str: string, substr: string): number { } describe('ppr-metadata-blocking', () => { - const { next, isNextDev, isNextStart } = nextTestSetup({ + const { next, isNextDev, isNextStart, skipped } = nextTestSetup({ files: __dirname, + skipDeployment: true, }) + if (skipped) return // No dynamic APIs used in metadata describe('static metadata', () => { From 5e54947edcce72af7b5e7215808b3aab855cc7f9 Mon Sep 17 00:00:00 2001 From: Jiachi Liu <inbox@huozhi.im> Date: Tue, 11 Feb 2025 02:52:39 +0100 Subject: [PATCH 7/7] skipped deployment for private env --- .../ppr-metadata-blocking-ppr-fallback.test.ts | 5 ++++- .../ppr-metadata-blocking/ppr-metadata-blocking.test.ts | 4 +--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking-ppr-fallback.test.ts b/test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking-ppr-fallback.test.ts index 3dbb119f53c8e..1ae5b7e2b4395 100644 --- a/test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking-ppr-fallback.test.ts +++ b/test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking-ppr-fallback.test.ts @@ -5,13 +5,16 @@ function countSubstring(str: string, substr: string): number { } describe('ppr-metadata-blocking-ppr-fallback', () => { - const { next } = nextTestSetup({ + const { next, skipped } = nextTestSetup({ files: __dirname, + skipDeployment: true, env: { __NEXT_EXPERIMENTAL_STATIC_SHELL_DEBUGGING: '1', }, }) + if (skipped) return + it('should not include metadata in partial shell when page is fully dynamic', async () => { const $ = await next.render$('/fully-dynamic?__nextppronly=fallback') expect(countSubstring($.html(), '<title>')).toBe(0) diff --git a/test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking.test.ts b/test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking.test.ts index e26899bda5c50..20dbf3655bc96 100644 --- a/test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking.test.ts +++ b/test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking.test.ts @@ -7,11 +7,9 @@ function countSubstring(str: string, substr: string): number { } describe('ppr-metadata-blocking', () => { - const { next, isNextDev, isNextStart, skipped } = nextTestSetup({ + const { next, isNextDev, isNextStart } = nextTestSetup({ files: __dirname, - skipDeployment: true, }) - if (skipped) return // No dynamic APIs used in metadata describe('static metadata', () => {