Skip to content

Commit

Permalink
Add default head for app dir (#43963)
Browse files Browse the repository at this point in the history
Add default head to app dir, when there's no`head.js`, use the default
head with the following meta tags

```html
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
```

It will be replaced if there's custom head.js in child layout.

NEXT-169
  • Loading branch information
huozhi committed Dec 13, 2022
1 parent 1484f40 commit 43680e3
Show file tree
Hide file tree
Showing 5 changed files with 27 additions and 7 deletions.
10 changes: 10 additions & 0 deletions packages/next/client/components/head.tsx
@@ -0,0 +1,10 @@
import React from 'react'

export function DefaultHead() {
return (
<>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</>
)
}
13 changes: 8 additions & 5 deletions packages/next/server/app-render.tsx
Expand Up @@ -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'

Expand Down Expand Up @@ -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<React.ReactNode> {
// Handle dynamic segment params.
const segmentParam = getDynamicParamFromSegment(segment)
Expand All @@ -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
}
Expand All @@ -1038,6 +1040,8 @@ export async function renderToHTMLOrFlight(
if (head) {
const Head = await interopDefault(await head[0]())
return <Head params={currentParams} />
} else if (isRootHead) {
return <DefaultHead />
}

return null
Expand Down Expand Up @@ -1451,7 +1455,6 @@ export async function renderToHTMLOrFlight(
))
: null}
<Component {...props} />
{/* {HeadTags ? <HeadTags /> : null} */}
</>
)
},
Expand Down Expand Up @@ -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 = [
Expand Down Expand Up @@ -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
Expand Down
3 changes: 1 addition & 2 deletions test/e2e/app-dir/app/pages/index.js
Expand Up @@ -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 (
<>
<b>rc:{React.Component ? 'c' : 'no'}</b>
<p className={styles.content}>hello from pages/index</p>
<Link href="/dashboard">Dashboard</Link>
<div>{data}</div>
Expand Down
4 changes: 4 additions & 0 deletions test/e2e/app-dir/head.test.ts
Expand Up @@ -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(
'<meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/>'
)
expect(headTags.find((el) => el.attribs.src === '/hello.js')).toBeTruthy()
expect(
headTags.find((el) => el.attribs.src === '/another.js')
Expand Down
4 changes: 4 additions & 0 deletions test/e2e/app-dir/rsc-basic.test.ts
Expand Up @@ -112,6 +112,10 @@ describe('app dir - rsc basics', () => {

// should have only 1 DOCTYPE
expect(homeHTML).toMatch(/^<!DOCTYPE html><html/)
// should have default head when there's no head.js provided
expect(homeHTML).toContain(
'<meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/>'
)
expect(homeHTML).toContain('component:index.server')
expect(homeHTML).toContain('header:test-util')

Expand Down

0 comments on commit 43680e3

Please sign in to comment.