diff --git a/packages/next/client/components/head.tsx b/packages/next/client/components/head.tsx new file mode 100644 index 000000000000..0b123102d6c7 --- /dev/null +++ b/packages/next/client/components/head.tsx @@ -0,0 +1,10 @@ +import React from 'react' + +export function DefaultHead() { + return ( + <> + + + + ) +} diff --git a/packages/next/server/app-render.tsx b/packages/next/server/app-render.tsx index be0bf6eb63d8..ef6ed8766ca7 100644 --- a/packages/next/server/app-render.tsx +++ b/packages/next/server/app-render.tsx @@ -46,6 +46,7 @@ import { FLIGHT_PARAMETERS, } from '../client/components/app-router-headers' import type { StaticGenerationStore } from '../client/components/static-generation-async-storage' +import { DefaultHead } from '../client/components/head' const isEdgeRuntime = process.env.NEXT_RUNTIME === 'edge' @@ -1011,7 +1012,8 @@ export async function renderToHTMLOrFlight( async function resolveHead( [segment, parallelRoutes, { head }]: LoaderTree, - parentParams: { [key: string]: any } + parentParams: { [key: string]: any }, + isRootHead: boolean ): Promise { // Handle dynamic segment params. const segmentParam = getDynamicParamFromSegment(segment) @@ -1029,7 +1031,7 @@ export async function renderToHTMLOrFlight( parentParams for (const key in parallelRoutes) { const childTree = parallelRoutes[key] - const returnedHead = await resolveHead(childTree, currentParams) + const returnedHead = await resolveHead(childTree, currentParams, false) if (returnedHead) { return returnedHead } @@ -1038,6 +1040,8 @@ export async function renderToHTMLOrFlight( if (head) { const Head = await interopDefault(await head[0]()) return + } else if (isRootHead) { + return } return null @@ -1451,7 +1455,6 @@ export async function renderToHTMLOrFlight( )) : null} - {/* {HeadTags ? : null} */} ) }, @@ -1585,7 +1588,7 @@ export async function renderToHTMLOrFlight( return [actualSegment] } - const rscPayloadHead = await resolveHead(loaderTree, {}) + const rscPayloadHead = await resolveHead(loaderTree, {}, true) // Flight data that is going to be passed to the browser. // Currently a single item array but in the future multiple patches might be combined in a single request. const flightData: FlightData = [ @@ -1656,7 +1659,7 @@ export async function renderToHTMLOrFlight( } : {} - const initialHead = await resolveHead(loaderTree, {}) + const initialHead = await resolveHead(loaderTree, {}, true) /** * A new React Component that renders the provided React Component diff --git a/test/e2e/app-dir/app/pages/index.js b/test/e2e/app-dir/app/pages/index.js index 9f0223e8b3d7..ac03305fde3c 100644 --- a/test/e2e/app-dir/app/pages/index.js +++ b/test/e2e/app-dir/app/pages/index.js @@ -3,11 +3,10 @@ import Link from 'next/link' import useSWR from 'swr' import styles from '../styles/shared.module.css' -export default function Page(props) { +export default function Page() { const { data } = useSWR('swr-index', (v) => v, { fallbackData: 'swr-index' }) return ( <> - rc:{React.Component ? 'c' : 'no'}

hello from pages/index

Dashboard
{data}
diff --git a/test/e2e/app-dir/head.test.ts b/test/e2e/app-dir/head.test.ts index b9739f5938f8..47fb252a4ea6 100644 --- a/test/e2e/app-dir/head.test.ts +++ b/test/e2e/app-dir/head.test.ts @@ -39,6 +39,10 @@ describe('app dir head', () => { const $ = cheerio.load(html) const headTags = $('head').children().toArray() + // should not include default tags in page with head.js provided + expect(html).not.toContain( + '' + ) expect(headTags.find((el) => el.attribs.src === '/hello.js')).toBeTruthy() expect( headTags.find((el) => el.attribs.src === '/another.js') diff --git a/test/e2e/app-dir/rsc-basic.test.ts b/test/e2e/app-dir/rsc-basic.test.ts index 713806074c16..d7fb47439f6c 100644 --- a/test/e2e/app-dir/rsc-basic.test.ts +++ b/test/e2e/app-dir/rsc-basic.test.ts @@ -112,6 +112,10 @@ describe('app dir - rsc basics', () => { // should have only 1 DOCTYPE expect(homeHTML).toMatch(/^' + ) expect(homeHTML).toContain('component:index.server') expect(homeHTML).toContain('header:test-util')