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 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..1ae5b7e2b4395 --- /dev/null +++ b/test/e2e/app-dir/ppr-metadata-blocking/ppr-metadata-blocking-ppr-fallback.test.ts @@ -0,0 +1,37 @@ +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, 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(), '')).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..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,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, }) @@ -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,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($('head title').text()).toBe('dynamic-metadata - partial') - expect(countSubstring($.html(), '<title>')).toBe(1) + // 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( @@ -133,6 +139,9 @@ describe('ppr-metadata-blocking', () => { // Dynamic render should not have postponed header const headers = res1.headers + // 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(null) const $1 = cheerio.load(await res1.text()) @@ -142,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).toBeGreaterThan(attribute1) + expect(attribute2).not.toEqual(attribute1) expect(attribute1).toBeTruthy() }) })