Skip to content

Commit

Permalink
Use metadata API in automatically created root layout (#45938)
Browse files Browse the repository at this point in the history
Use the Metadata API instead of creating a `head.js` when automatically
creating a root layout. The generated layout is the same as the one in
#45819, but with a different title
and description.

Automatic root layout:
```tsx
export const metadata = {
  title: 'Next.js',
  description: 'Generated by Next.js',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}
```

Fixes NEXT-545

## Bug

- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Errors have a helpful link attached, see
[`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md)

## Feature

- [ ] Implements an existing feature request or RFC. Make sure the
feature request has been accepted for implementation before opening a
PR.
- [ ] Related issues linked using `fixes #number`
- [ ]
[e2e](https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs)
tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have a helpful link attached, see
[`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md)

## Documentation / Examples

- [ ] Make sure the linting passes by running `pnpm build && pnpm lint`
- [ ] The "examples guidelines" are followed from [our contributing
doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
  • Loading branch information
hanneslund authored Feb 16, 2023
1 parent 8d84dca commit 0484dc4
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 122 deletions.
54 changes: 16 additions & 38 deletions packages/next/src/lib/verifyRootLayout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,45 +18,40 @@ const glob = (cwd: string, pattern: string): Promise<string[]> => {

function getRootLayout(isTs: boolean) {
if (isTs) {
return `export default function RootLayout({
return `export const metadata = {
title: 'Next.js',
description: 'Generated by Next.js',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html>
<head />
<html lang="en">
<body>{children}</body>
</html>
)
}
`
}

return `export default function RootLayout({ children }) {
return (
<html>
<head />
return `export const metadata = {
title: 'Next.js',
description: 'Generated by Next.js',
}
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
`
}

function getHead() {
return `export default function Head() {
return (
<>
<title></title>
<meta content="width=device-width, initial-scale=1" name="viewport" />
<link rel="icon" href="/favicon.ico" />
</>
)
}
`
}

export async function verifyRootLayout({
dir,
appDir,
Expand Down Expand Up @@ -120,31 +115,14 @@ export async function verifyRootLayout({
`layout.${hasTsConfig ? 'tsx' : 'js'}`
)
await fs.writeFile(rootLayoutPath, getRootLayout(hasTsConfig))
const headPath = path.join(
appDir,
availableDir,
`head.${hasTsConfig ? 'tsx' : 'js'}`
)
const hasHead = await fs.access(headPath).then(
() => true,
() => false
)

if (!hasHead) {
await fs.writeFile(headPath, getHead())
}

console.log(
chalk.green(
`\nYour page ${chalk.bold(
`app/${normalizedPagePath}`
)} did not have a root layout. We created ${chalk.bold(
`app${rootLayoutPath.replace(appDir, '')}`
)}${
!hasHead
? ` and ${chalk.bold(`app${headPath.replace(appDir, '')}`)}`
: ''
} for you.`
)} for you.`
) + '\n'
)

Expand Down
130 changes: 46 additions & 84 deletions test/e2e/app-dir/create-root-layout/create-root-layout.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,29 +46,20 @@ describe('app-dir create root layout', () => {
/did not have a root layout/
)
expect(stripAnsi(next.cliOutput.slice(outputIndex))).toMatch(
'Your page app/route/page.js did not have a root layout. We created app/layout.js and app/head.js for you.'
'Your page app/route/page.js did not have a root layout. We created app/layout.js for you.'
)

expect(await next.readFile('app/layout.js')).toMatchInlineSnapshot(`
"export default function RootLayout({ children }) {
return (
<html>
<head />
<body>{children}</body>
</html>
)
}
"
`)

expect(await next.readFile('app/head.js')).toMatchInlineSnapshot(`
"export default function Head() {
return (
<>
<title></title>
<meta content=\\"width=device-width, initial-scale=1\\" name=\\"viewport\\" />
<link rel=\\"icon\\" href=\\"/favicon.ico\\" />
</>
"export const metadata = {
title: 'Next.js',
description: 'Generated by Next.js',
}
export default function RootLayout({ children }) {
return (
<html lang=\\"en\\">
<body>{children}</body>
</html>
)
}
"
Expand Down Expand Up @@ -106,31 +97,21 @@ describe('app-dir create root layout', () => {
/did not have a root layout/
)
expect(stripAnsi(next.cliOutput.slice(outputIndex))).toInclude(
'Your page app/(group)/page.js did not have a root layout. We created app/(group)/layout.js and app/(group)/head.js for you.'
'Your page app/(group)/page.js did not have a root layout. We created app/(group)/layout.js for you.'
)

expect(await next.readFile('app/(group)/layout.js'))
.toMatchInlineSnapshot(`
"export default function RootLayout({ children }) {
return (
<html>
<head />
<body>{children}</body>
</html>
)
}
"
`)

expect(await next.readFile('app/(group)/head.js'))
.toMatchInlineSnapshot(`
"export default function Head() {
return (
<>
<title></title>
<meta content=\\"width=device-width, initial-scale=1\\" name=\\"viewport\\" />
<link rel=\\"icon\\" href=\\"/favicon.ico\\" />
</>
"export const metadata = {
title: 'Next.js',
description: 'Generated by Next.js',
}
export default function RootLayout({ children }) {
return (
<html lang=\\"en\\">
<body>{children}</body>
</html>
)
}
"
Expand Down Expand Up @@ -168,31 +149,21 @@ describe('app-dir create root layout', () => {
/did not have a root layout/
)
expect(stripAnsi(next.cliOutput.slice(outputIndex))).toInclude(
'Your page app/(group)/route/second/inner/page.js did not have a root layout. We created app/(group)/route/second/layout.js and app/(group)/route/second/head.js for you.'
'Your page app/(group)/route/second/inner/page.js did not have a root layout. We created app/(group)/route/second/layout.js for you.'
)

expect(await next.readFile('app/(group)/route/second/layout.js'))
.toMatchInlineSnapshot(`
"export default function RootLayout({ children }) {
return (
<html>
<head />
<body>{children}</body>
</html>
)
}
"
`)

expect(await next.readFile('app/(group)/route/second/head.js'))
.toMatchInlineSnapshot(`
"export default function Head() {
return (
<>
<title></title>
<meta content=\\"width=device-width, initial-scale=1\\" name=\\"viewport\\" />
<link rel=\\"icon\\" href=\\"/favicon.ico\\" />
</>
"export const metadata = {
title: 'Next.js',
description: 'Generated by Next.js',
}
export default function RootLayout({ children }) {
return (
<html lang=\\"en\\">
<body>{children}</body>
</html>
)
}
"
Expand Down Expand Up @@ -229,33 +200,24 @@ describe('app-dir create root layout', () => {
/did not have a root layout/
)
expect(stripAnsi(next.cliOutput.slice(outputIndex))).toInclude(
'Your page app/page.tsx did not have a root layout. We created app/layout.tsx and app/head.tsx for you.'
'Your page app/page.tsx did not have a root layout. We created app/layout.tsx for you.'
)

expect(await next.readFile('app/layout.tsx')).toMatchInlineSnapshot(`
"export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html>
<head />
<body>{children}</body>
</html>
)
}
"
`)
"export const metadata = {
title: 'Next.js',
description: 'Generated by Next.js',
}
expect(await next.readFile('app/head.tsx')).toMatchInlineSnapshot(`
"export default function Head() {
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<>
<title></title>
<meta content=\\"width=device-width, initial-scale=1\\" name=\\"viewport\\" />
<link rel=\\"icon\\" href=\\"/favicon.ico\\" />
</>
<html lang=\\"en\\">
<body>{children}</body>
</html>
)
}
"
Expand Down

0 comments on commit 0484dc4

Please sign in to comment.