From 747d365004e722b7806ba4560edd4d85b51cb67e Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Mon, 9 Sep 2024 10:57:31 +0200 Subject: [PATCH] Fix metadata prop merging (#69807) ### What Fixes metadata merging issue on 14.2. There's a bug with React on 14.2 as it bit outdated: while generating rsc payload, if we use fragment to wrap the metadata and noindex component, it will miss all the data. It works well with separating them as an array. It also requires #64058 to be applied as well so the output not-found page will having correct status 404, and then we can generate noindex metatag based on correct status Fixes #69778 --- .../next/src/server/app-render/app-render.tsx | 14 ++++----- .../app/(cart)/product/layout.tsx | 13 ++++++++ .../app/(cart)/product/page.tsx | 3 ++ .../app/@header/layout.tsx | 7 +++++ .../metadata-navigation/app/@header/page.tsx | 12 ++++++++ .../app/@header/product/layout.tsx | 7 +++++ .../app/@header/product/page.tsx | 12 ++++++++ .../metadata-navigation/app/layout.tsx | 23 ++++++++++++++ .../app-dir/metadata-navigation/app/page.tsx | 7 +++++ .../metadata-navigation.test.ts | 30 +++++++++++++++++++ 10 files changed, 120 insertions(+), 8 deletions(-) create mode 100644 test/e2e/app-dir/metadata-navigation/app/(cart)/product/layout.tsx create mode 100644 test/e2e/app-dir/metadata-navigation/app/(cart)/product/page.tsx create mode 100644 test/e2e/app-dir/metadata-navigation/app/@header/layout.tsx create mode 100644 test/e2e/app-dir/metadata-navigation/app/@header/page.tsx create mode 100644 test/e2e/app-dir/metadata-navigation/app/@header/product/layout.tsx create mode 100644 test/e2e/app-dir/metadata-navigation/app/@header/product/page.tsx create mode 100644 test/e2e/app-dir/metadata-navigation/app/layout.tsx create mode 100644 test/e2e/app-dir/metadata-navigation/app/page.tsx create mode 100644 test/e2e/app-dir/metadata-navigation/metadata-navigation.test.ts diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index d0005eb6e7148..d192970eec658 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -319,13 +319,11 @@ async function generateFlight( flightRouterState, isFirst: true, // For flight, render metadata inside leaf page - rscPayloadHead: ( - <> - - {/* Adding requestId as react key to make metadata remount for each render */} - - - ), + // NOTE: in 14.2, fragment doesn't work well with React, using array instead + rscPayloadHead: [ + , + , + ], injectedCSS: new Set(), injectedJS: new Set(), injectedFontPreloadTags: new Set(), @@ -531,12 +529,12 @@ async function ReactServerError({ const head = ( <> - {/* Adding requestId as react key to make metadata remount for each render */} {process.env.NODE_ENV === 'development' && ( )} + ) diff --git a/test/e2e/app-dir/metadata-navigation/app/(cart)/product/layout.tsx b/test/e2e/app-dir/metadata-navigation/app/(cart)/product/layout.tsx new file mode 100644 index 0000000000000..1fae4138eb8a2 --- /dev/null +++ b/test/e2e/app-dir/metadata-navigation/app/(cart)/product/layout.tsx @@ -0,0 +1,13 @@ +import { Metadata } from 'next' + +export const metadata: Metadata = { + title: 'Product Layout', +} + +export default function Layout({ + children, +}: Readonly<{ + children: React.ReactNode +}>) { + return <>{children} +} diff --git a/test/e2e/app-dir/metadata-navigation/app/(cart)/product/page.tsx b/test/e2e/app-dir/metadata-navigation/app/(cart)/product/page.tsx new file mode 100644 index 0000000000000..6fda1b09db3e0 --- /dev/null +++ b/test/e2e/app-dir/metadata-navigation/app/(cart)/product/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return
Product page content
+} diff --git a/test/e2e/app-dir/metadata-navigation/app/@header/layout.tsx b/test/e2e/app-dir/metadata-navigation/app/@header/layout.tsx new file mode 100644 index 0000000000000..15286d515a67f --- /dev/null +++ b/test/e2e/app-dir/metadata-navigation/app/@header/layout.tsx @@ -0,0 +1,7 @@ +type LayoutProps = { + children: React.ReactNode +} + +export default function Layout({ children }: LayoutProps) { + return
{children}
+} diff --git a/test/e2e/app-dir/metadata-navigation/app/@header/page.tsx b/test/e2e/app-dir/metadata-navigation/app/@header/page.tsx new file mode 100644 index 0000000000000..029e01e2ad355 --- /dev/null +++ b/test/e2e/app-dir/metadata-navigation/app/@header/page.tsx @@ -0,0 +1,12 @@ +import Link from 'next/link' + +export default function Page() { + return ( +
+
Home header
+ + Product + +
+ ) +} diff --git a/test/e2e/app-dir/metadata-navigation/app/@header/product/layout.tsx b/test/e2e/app-dir/metadata-navigation/app/@header/product/layout.tsx new file mode 100644 index 0000000000000..cd0e8d61beece --- /dev/null +++ b/test/e2e/app-dir/metadata-navigation/app/@header/product/layout.tsx @@ -0,0 +1,7 @@ +type LayoutProps = { + children: React.ReactNode +} + +export default function Layout({ children }: LayoutProps) { + return <>{children} +} diff --git a/test/e2e/app-dir/metadata-navigation/app/@header/product/page.tsx b/test/e2e/app-dir/metadata-navigation/app/@header/product/page.tsx new file mode 100644 index 0000000000000..11b27614e0630 --- /dev/null +++ b/test/e2e/app-dir/metadata-navigation/app/@header/product/page.tsx @@ -0,0 +1,12 @@ +import Link from 'next/link' + +export default function Page() { + return ( +
+

Product header

+ + Go to Home page + +
+ ) +} diff --git a/test/e2e/app-dir/metadata-navigation/app/layout.tsx b/test/e2e/app-dir/metadata-navigation/app/layout.tsx new file mode 100644 index 0000000000000..e05cd2dd3ed58 --- /dev/null +++ b/test/e2e/app-dir/metadata-navigation/app/layout.tsx @@ -0,0 +1,23 @@ +import type { Metadata } from 'next' + +export const metadata: Metadata = { + title: 'Home Layout', + description: 'Generated by create next app', +} + +export default function RootLayout({ + header, + children, +}: Readonly<{ + header: React.ReactNode + children: React.ReactNode +}>) { + return ( + + + {header} + {children} + + + ) +} diff --git a/test/e2e/app-dir/metadata-navigation/app/page.tsx b/test/e2e/app-dir/metadata-navigation/app/page.tsx new file mode 100644 index 0000000000000..92b0c4de98772 --- /dev/null +++ b/test/e2e/app-dir/metadata-navigation/app/page.tsx @@ -0,0 +1,7 @@ +export default function Home() { + return ( +
+

Home page content

+
+ ) +} diff --git a/test/e2e/app-dir/metadata-navigation/metadata-navigation.test.ts b/test/e2e/app-dir/metadata-navigation/metadata-navigation.test.ts new file mode 100644 index 0000000000000..10accfbc6d7c4 --- /dev/null +++ b/test/e2e/app-dir/metadata-navigation/metadata-navigation.test.ts @@ -0,0 +1,30 @@ +import { createNextDescribe } from 'e2e-utils' + +createNextDescribe( + 'app-dir - metadata-navigation', + { + files: __dirname, + }, + ({ next }) => { + it('should show the index title', async () => { + const browser = await next.browser('/') + expect(await browser.elementByCss('title').text()).toBe('Home Layout') + }) + + it('should show target page metadata after navigation', async () => { + const browser = await next.browser('/') + await browser.elementByCss('#product-link').click() + await browser.waitForElementByCss('#product-title') + expect(await browser.elementByCss('title').text()).toBe('Product Layout') + }) + + it('should show target page metadata after navigation with back', async () => { + const browser = await next.browser('/') + await browser.elementByCss('#product-link').click() + await browser.waitForElementByCss('#product-title') + await browser.elementByCss('#home-link').click() + await browser.waitForElementByCss('#home-title') + expect(await browser.elementByCss('title').text()).toBe('Home Layout') + }) + } +)