Next.js 14 App Router中MDX元数据无法在布局组件获取的问题
解决Next.js 14 App Router中Layout无法获取MDX页面Meta数据的方案
方法1:拆分Meta到单独文件(无额外依赖,推荐)
将每个组件的Meta数据单独维护在meta.ts文件中,同时供MDX页面和Layout复用,避免重复编写。
- 创建组件Meta文件
在每个组件文档文件夹下新建meta.ts,导出统一格式的Meta数据:
// components/Button/meta.ts export const meta = { title: "Button 按钮", description: "用于触发操作的按钮组件", category: "基础组件" };
- MDX页面复用Meta
在page.mdx中导入Meta并导出为页面元数据,同时在内容中复用:
import { meta } from "./meta"; // 导出给Next.js的页面元数据 export const metadata = meta; # {meta.title} {meta.description} // 组件文档内容...
- Layout动态获取Meta
在组件文档的Layout中,通过路由参数动态导入对应组件的meta.ts:
// app/components/[component]/layout.tsx type Props = { params: { component: string }; children: React.ReactNode; }; export default async function ComponentLayout({ params, children }: Props) { // 动态导入对应组件的Meta文件 const { meta } = await import(`../../components/${params.component}/meta.ts`); return ( <div className="component-docs-layout"> <aside> <h2>{meta.category}</h2> <h3>{meta.title}</h3> </aside> <main>{children}</main> </div> ); }
方法2:使用Contentlayer统一管理MDX文档
通过Contentlayer在构建阶段提取MDX的Frontmatter或导出变量,让Layout可以直接查询文档数据。
- 安装依赖
npm install contentlayer next-contentlayer
- 配置Contentlayer
在项目根目录创建contentlayer.config.ts,定义文档类型和提取规则:
// contentlayer.config.ts import { defineDocumentType, makeSource } from "contentlayer/source-files"; export const ComponentDoc = defineDocumentType(() => ({ name: "ComponentDoc", filePathPattern: "components/**/page.mdx", // 定义需要提取的Meta字段 fields: { title: { type: "string", required: true }, description: { type: "string", required: true }, category: { type: "string", required: true } }, // 生成用于匹配路由的slug computedFields: { slug: { type: "string", resolve: (doc) => doc._raw.flattenedPath.split("/").at(-2)! } } })); export default makeSource({ contentDirPath: ".", documentTypes: [ComponentDoc] });
- 配置Next.js
修改next.config.js集成Contentlayer:
// next.config.js const { withContentlayer } = require("next-contentlayer"); const nextConfig = {}; module.exports = withContentlayer(nextConfig);
- Layout中查询Meta数据
在组件文档Layout中,通过路由参数匹配对应的文档并获取Meta:
// app/components/[component]/layout.tsx import { allComponentDocs } from "contentlayer/generated"; type Props = { params: { component: string }; children: React.ReactNode; }; export default function ComponentLayout({ params, children }: Props) { const componentDoc = allComponentDocs.find(doc => doc.slug === params.component); if (!componentDoc) throw new Error("组件文档未找到"); return ( <div className="component-docs-layout"> <aside> <h2>{componentDoc.category}</h2> <h3>{componentDoc.title}</h3> </aside> <main>{children}</main> </div> ); }
- MDX页面中定义Meta
在page.mdx顶部通过Frontmatter定义Meta:
--- title: "Button 按钮" description: "用于触发操作的按钮组件" category: "基础组件" --- # Button 按钮 // 组件文档内容...
方法3:使用React Context传递Meta(客户端组件方案)
通过React Context将MDX页面的Meta传递给父级Layout,适合需要客户端交互的场景。
- 创建Meta Context
// app/components/ComponentMetaContext.tsx "use client"; import { createContext, useContext } from "react"; type ComponentMeta = { title: string; description: string; category: string; }; const ComponentMetaContext = createContext<ComponentMeta | null>(null); export function ComponentMetaProvider({ children, meta }: { children: React.ReactNode; meta: ComponentMeta }) { return ( <ComponentMetaContext.Provider value={meta}> {children} </ComponentMetaContext.Provider> ); } export function useComponentMeta() { const context = useContext(ComponentMetaContext); if (!context) throw new Error("useComponentMeta必须在ComponentMetaProvider内使用"); return context; }
- MDX页面注入Context
在page.mdx中导出Meta并包裹Context Provider:
import { ComponentMetaProvider } from "../ComponentMetaContext"; export const meta = { title: "Button 按钮", description: "用于触发操作的按钮组件", category: "基础组件" }; # {meta.title} {meta.description} // 组件文档内容... export default function Page(props) { return ( <ComponentMetaProvider meta={meta}> <main {...props} /> </ComponentMetaProvider> ); }
- Layout中读取Context
将Layout改为客户端组件,通过Hook获取Meta:
// app/components/[component]/layout.tsx "use client"; import { useComponentMeta } from "../ComponentMetaContext"; export default function ComponentLayout({ children }: { children: React.ReactNode }) { const meta = useComponentMeta(); return ( <div className="component-docs-layout"> <aside> <h2>{meta.category}</h2> <h3>{meta.title}</h3> </aside> {children} </div> ); }
内容的提问来源于stack exchange,提问作者sebazelonka
相关产品推荐
相关产品推荐

