Skip to content

Commit ca67a19

Browse files
authoredFeb 5, 2025··
Remove requirement page.{jsx,tsx} pages to have exported metadata object (#4154)
* upd * upd * upd * upd * upd * upd * upd * upd * upd * fix * prettier * fix tests * fix tests * upd * [skip ci] * upd * upd
1 parent 1b44417 commit ca67a19

File tree

15 files changed

+241
-36
lines changed

15 files changed

+241
-36
lines changed
 

‎.changeset/spotty-carrots-jump.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"nextra": patch
3+
"nextra-theme-blog": patch
4+
"nextra-theme-docs": patch
5+
---
6+
7+
remove requirement `page.{jsx,tsx}` pages to have exported `metadata` object

‎docs/app/layout.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export const metadata: Metadata = {
4949

5050
const banner = (
5151
<Banner dismissible={false}>
52-
🎉 Nextra 4.0 is released. Dima Machina is looking{' '}
52+
🎉 Nextra 4.0 is released. dimaMachina is looking{' '}
5353
<Link href="https://github.com/dimaMachina">
5454
for a new job or consulting
5555
</Link>

‎examples/docs/src/app/blog/page.jsx

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
export const metadata = {}
2-
31
export default function BlogPage() {
42
return (
53
<h1

‎examples/docs/src/app/layout.jsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export default async function RootLayout({ children }) {
3737
chatLink="https://discord.gg/hEM84NMkRv"
3838
/>
3939
)
40+
const pageMap = await getPageMap()
4041
return (
4142
<html lang="en" dir="ltr" suppressHydrationWarning>
4243
<Head faviconGlyph="✦" />
@@ -48,7 +49,7 @@ export default async function RootLayout({ children }) {
4849
editLink="Edit this page on GitHub"
4950
docsRepositoryBase="https://github.com/shuding/nextra/blob/main/examples/docs"
5051
sidebar={{ defaultMenuCollapseLevel: 1 }}
51-
pageMap={await getPageMap()}
52+
pageMap={pageMap}
5253
>
5354
{children}
5455
</Layout>

‎examples/docs/src/app/page.jsx

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
export const metadata = {}
2-
31
export default function IndexPage() {
42
return (
53
<h1

‎examples/docs/src/app/showcase/(overview)/page.jsx

-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
/**
2-
* @see https://github.com/shuding/nextra/issues/4148
3-
*/
4-
5-
export const metadata = {}
6-
71
export default function Page() {
82
return (
93
<h1

‎examples/docs/src/content/themes/docs/bleed.mdx

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
export const metadata = {
2-
sidebarTitle: 'Bleed'
3-
}
1+
---
2+
sidebarTitle: Bleed
3+
---
44

55
# Bleed Component
66

‎examples/docs/src/content/themes/docs/callout.mdx

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
export const metadata = {
2-
sidebarTitle: 'Callout'
3-
}
1+
---
2+
sidebarTitle: Callout
3+
---
44

55
# Callout Component
66

‎examples/docs/src/content/themes/docs/tabs.mdx

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
export const metadata = {
2-
sidebarTitle: 'Tabs'
3-
}
1+
---
2+
sidebarTitle: Tabs
3+
---
44

55
# Tabs Component
66

‎examples/swr-site/content/en/_meta.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ export default {
33
type: 'page',
44
display: 'hidden',
55
theme: {
6-
typesetting: 'article'
6+
typesetting: 'article',
7+
toc: false
78
}
89
},
910
docs: {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import path from 'node:path'
2+
import { CWD } from '../constants.js'
3+
import { findMetaAndPageFilePaths } from '../page-map/find-meta-and-page-file-paths.js'
4+
import { convertPageMapToJs } from '../page-map/to-js.js'
5+
import { convertToPageMap } from '../page-map/to-page-map.js'
6+
7+
describe('convertPageMapToJs()', () => {
8+
it('should work for docs example', async () => {
9+
const cwd = path.join(CWD, '..', '..', 'examples', 'docs')
10+
const filePaths = await findMetaAndPageFilePaths({
11+
dir: path.join(cwd, 'src/app'),
12+
cwd,
13+
contentDir: 'src/content'
14+
})
15+
const { pageMap, mdxPages } = convertToPageMap({ filePaths })
16+
17+
const result = convertPageMapToJs({ pageMap, mdxPages })
18+
expect(result).toMatchInlineSnapshot(`
19+
"import { normalizePageMap, getMetadata } from 'nextra/page-map'
20+
21+
import meta from "private-next-root-dir/src/content/_meta.js";
22+
import features_meta from "private-next-root-dir/src/content/features/_meta.js";
23+
import {metadata as features_i18n} from "private-next-root-dir/src/content/features/i18n.mdx?metadata";
24+
import {metadata as features_image} from "private-next-root-dir/src/content/features/image.mdx?metadata";
25+
import {metadata as features_latex} from "private-next-root-dir/src/content/features/latex.mdx?metadata";
26+
import {metadata as features_mdx} from "private-next-root-dir/src/content/features/mdx.mdx?metadata";
27+
import {metadata as features_ssg} from "private-next-root-dir/src/content/features/ssg.mdx?metadata";
28+
import {metadata as features_themes} from "private-next-root-dir/src/content/features/themes.mdx?metadata";
29+
import themes_meta from "private-next-root-dir/src/content/themes/_meta.js";
30+
import themes_blog_meta from "private-next-root-dir/src/content/themes/blog/_meta.js";
31+
import {metadata as themes_blog_index} from "private-next-root-dir/src/content/themes/blog/index.mdx?metadata";
32+
import themes_docs_meta from "private-next-root-dir/src/content/themes/docs/_meta.js";
33+
import {metadata as themes_docs_bleed} from "private-next-root-dir/src/content/themes/docs/bleed.mdx?metadata";
34+
import {metadata as themes_docs_callout} from "private-next-root-dir/src/content/themes/docs/callout.mdx?metadata";
35+
import {metadata as themes_docs_configuration} from "private-next-root-dir/src/content/themes/docs/configuration.mdx?metadata";
36+
import {metadata as themes_docs_index} from "private-next-root-dir/src/content/themes/docs/index.mdx?metadata";
37+
import {metadata as themes_docs_tabs} from "private-next-root-dir/src/content/themes/docs/tabs.mdx?metadata";
38+
import * as src_app_blog_page from "private-next-root-dir/src/app/blog/page.jsx";
39+
import {metadata as index} from "private-next-root-dir/src/content/index.mdx?metadata";
40+
import * as src_app_showcase_overview_page from "private-next-root-dir/src/app/showcase/(overview)/page.jsx";
41+
import {metadata as advanced_code_highlighting} from "private-next-root-dir/src/content/advanced/code-highlighting.mdx?metadata";
42+
import {metadata as get_started} from "private-next-root-dir/src/content/get-started.mdx?metadata";
43+
import {metadata as mermaid} from "private-next-root-dir/src/content/mermaid.mdx?metadata";
44+
import {metadata as page} from "private-next-root-dir/src/content/page.mdx?metadata";
45+
46+
export const pageMap = normalizePageMap([{
47+
data: meta
48+
}, {
49+
name: "features",
50+
route: "/features",
51+
children: [{
52+
data: features_meta
53+
}, {
54+
name: "i18n",
55+
route: "/features/i18n",
56+
frontMatter: features_i18n
57+
}, {
58+
name: "image",
59+
route: "/features/image",
60+
frontMatter: features_image
61+
}, {
62+
name: "latex",
63+
route: "/features/latex",
64+
frontMatter: features_latex
65+
}, {
66+
name: "mdx",
67+
route: "/features/mdx",
68+
frontMatter: features_mdx
69+
}, {
70+
name: "ssg",
71+
route: "/features/ssg",
72+
frontMatter: features_ssg
73+
}, {
74+
name: "themes",
75+
route: "/features/themes",
76+
frontMatter: features_themes
77+
}]
78+
}, {
79+
name: "themes",
80+
route: "/themes",
81+
children: [{
82+
data: themes_meta
83+
}, {
84+
name: "blog",
85+
route: "/themes/blog",
86+
children: [{
87+
data: themes_blog_meta
88+
}, {
89+
name: "index",
90+
route: "/themes/blog",
91+
frontMatter: themes_blog_index
92+
}]
93+
}, {
94+
name: "docs",
95+
route: "/themes/docs",
96+
children: [{
97+
data: themes_docs_meta
98+
}, {
99+
name: "bleed",
100+
route: "/themes/docs/bleed",
101+
frontMatter: themes_docs_bleed
102+
}, {
103+
name: "callout",
104+
route: "/themes/docs/callout",
105+
frontMatter: themes_docs_callout
106+
}, {
107+
name: "configuration",
108+
route: "/themes/docs/configuration",
109+
frontMatter: themes_docs_configuration
110+
}, {
111+
name: "index",
112+
route: "/themes/docs",
113+
frontMatter: themes_docs_index
114+
}, {
115+
name: "tabs",
116+
route: "/themes/docs/tabs",
117+
frontMatter: themes_docs_tabs
118+
}]
119+
}]
120+
}, {
121+
name: "blog",
122+
route: "/blog",
123+
frontMatter: getMetadata(src_app_blog_page)
124+
}, {
125+
name: "index",
126+
route: "/",
127+
frontMatter: index
128+
}, {
129+
name: "showcase",
130+
route: "/showcase",
131+
frontMatter: getMetadata(src_app_showcase_overview_page)
132+
}, {
133+
name: "advanced",
134+
route: "/advanced",
135+
children: [{
136+
name: "code-highlighting",
137+
route: "/advanced/code-highlighting",
138+
frontMatter: advanced_code_highlighting
139+
}]
140+
}, {
141+
name: "get-started",
142+
route: "/get-started",
143+
frontMatter: get_started
144+
}, {
145+
name: "mermaid",
146+
route: "/mermaid",
147+
frontMatter: mermaid
148+
}, {
149+
name: "page",
150+
route: "/page",
151+
frontMatter: page
152+
}])
153+
154+
export const RouteToFilepath = {
155+
"": "index.mdx",
156+
"advanced/code-highlighting": "advanced/code-highlighting.mdx",
157+
"features/i18n": "features/i18n.mdx",
158+
"features/image": "features/image.mdx",
159+
"features/latex": "features/latex.mdx",
160+
"features/mdx": "features/mdx.mdx",
161+
"features/ssg": "features/ssg.mdx",
162+
"features/themes": "features/themes.mdx",
163+
"get-started": "get-started.mdx",
164+
"mermaid": "mermaid.mdx",
165+
"page": "page.mdx",
166+
"themes/blog": "themes/blog/index.mdx",
167+
"themes/docs/bleed": "themes/docs/bleed.mdx",
168+
"themes/docs/callout": "themes/docs/callout.mdx",
169+
"themes/docs/configuration": "themes/docs/configuration.mdx",
170+
"themes/docs": "themes/docs/index.mdx",
171+
"themes/docs/tabs": "themes/docs/tabs.mdx"
172+
}"
173+
`)
174+
})
175+
})

‎packages/nextra/src/server/__tests__/to-page-map.test.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,7 @@ describe('generatePageMap()', () => {
514514

515515
expect(convertPageMapToJs({ pageMap, mdxPages, globalMetaPath }))
516516
.toMatchInlineSnapshot(`
517-
"import { normalizePageMap, mergeMetaWithPageMap } from 'nextra/page-map'
517+
"import { normalizePageMap, mergeMetaWithPageMap, getMetadata } from 'nextra/page-map'
518518
import globalMeta from 'private-next-root-dir/app/_meta.global.tsx'
519519
import {metadata as app_about_page} from "private-next-root-dir/app/about/page.mdx?metadata";
520520
import {metadata as app_blog_page} from "private-next-root-dir/app/blog/page.mdx?metadata";
@@ -574,7 +574,7 @@ describe('generatePageMap()', () => {
574574
import {metadata as app_docs_guide_syntax_highlighting_page} from "private-next-root-dir/app/docs/guide/syntax-highlighting/page.mdx?metadata";
575575
import {metadata as app_docs_guide_turbopack_page} from "private-next-root-dir/app/docs/guide/turbopack/page.mdx?metadata";
576576
import {metadata as app_docs_page} from "private-next-root-dir/app/docs/page.mdx?metadata";
577-
import {metadata as app_page} from "private-next-root-dir/app/page.tsx";
577+
import * as app_page from "private-next-root-dir/app/page.tsx";
578578
import {metadata as app_showcase_page} from "private-next-root-dir/app/showcase/page.mdx?metadata";
579579
import {metadata as app_sponsors_page} from "private-next-root-dir/app/sponsors/page.mdx?metadata";
580580
@@ -845,7 +845,7 @@ describe('generatePageMap()', () => {
845845
}, {
846846
name: "index",
847847
route: "/",
848-
frontMatter: app_page
848+
frontMatter: getMetadata(app_page)
849849
}, {
850850
name: "showcase",
851851
route: "/showcase",
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
1+
import type { Metadata } from 'next'
2+
13
export { normalizePageMap } from './normalize.js'
24
export { convertToPageMap } from './to-page-map.js'
35
export { mergeMetaWithPageMap } from './merge-meta-with-page-map.js'
46
export { getPageMap } from './get.js'
57
export { createIndexPage } from './index-page.js'
8+
9+
export function getMetadata(
10+
page:
11+
| { metadata: Metadata }
12+
| { generateMetadata: (props: object) => Promise<Metadata> }
13+
): Promise<Metadata> | Metadata {
14+
return 'generateMetadata' in page ? page.generateMetadata({}) : page.metadata
15+
}

‎packages/nextra/src/server/page-map/to-ast.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import path from 'node:path'
22
import type { ArrayExpression } from 'estree'
33
import type { Import, TItem } from '../../types.js'
4+
import { MARKDOWN_EXTENSION_RE } from '../constants.js'
45
import { createAstObject } from '../utils.js'
56

67
function cleanFilePath(filePath: string): string {
@@ -38,7 +39,14 @@ export function convertPageMapToAst(
3839
return createAstObject({
3940
name: item.name,
4041
route: item.route,
41-
frontMatter: { type: 'Identifier', name: importName }
42+
frontMatter: MARKDOWN_EXTENSION_RE.test(filePath)
43+
? { type: 'Identifier', name: importName }
44+
: {
45+
type: 'CallExpression',
46+
callee: { type: 'Identifier', name: 'getMetadata' },
47+
arguments: [{ type: 'Identifier', name: importName }],
48+
optional: false
49+
}
4250
})
4351
}
4452
const filePath = item.__metaPath

‎packages/nextra/src/server/page-map/to-js.ts

+23-10
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,20 @@ export function convertPageMapToJs({
2929
value: `private-next-root-dir/${filePath}${isMdx ? METADATA_ONLY_RQ : ''}`
3030
},
3131
specifiers: [
32-
{
33-
local: { type: 'Identifier', name: importName },
34-
...(isMeta
35-
? { type: 'ImportDefaultSpecifier' }
36-
: {
37-
type: 'ImportSpecifier',
38-
imported: { type: 'Identifier', name: 'metadata' }
39-
})
40-
}
32+
isMeta || isMdx
33+
? {
34+
local: { type: 'Identifier', name: importName },
35+
...(isMeta
36+
? { type: 'ImportDefaultSpecifier' }
37+
: {
38+
type: 'ImportSpecifier',
39+
imported: { type: 'Identifier', name: 'metadata' }
40+
})
41+
}
42+
: {
43+
type: 'ImportNamespaceSpecifier',
44+
local: { type: 'Identifier', name: importName }
45+
}
4146
]
4247
}
4348
}
@@ -60,7 +65,15 @@ export function convertPageMapToJs({
6065
pageMapRawJs = `mergeMetaWithPageMap(${pageMapRawJs}, globalMeta)`
6166
}
6267

63-
const rawJs = `import { ${['normalizePageMap', globalMetaPath && 'mergeMetaWithPageMap'].filter(Boolean).join(', ')} } from 'nextra/page-map'
68+
const rawJs = `import { ${[
69+
'normalizePageMap',
70+
globalMetaPath && 'mergeMetaWithPageMap',
71+
imports.some(
72+
o => !MARKDOWN_EXTENSION_RE.test(o.filePath) && !META_RE.test(o.filePath)
73+
) && 'getMetadata'
74+
]
75+
.filter(Boolean)
76+
.join(', ')} } from 'nextra/page-map'
6477
${globalMetaPath ? `import globalMeta from 'private-next-root-dir/${globalMetaPath}'` : ''}
6578
${importsResult.value}
6679
export const pageMap = normalizePageMap(${pageMapRawJs})

0 commit comments

Comments
 (0)
Please sign in to comment.