安装
npm install next-mdx-remote
如果使用涡轮包装,你需要添加以下 next.config.js 直至 这个问题 解决:
const nextConfig = {
+ transpilePackages: ['next-mdx-remote'],
}
实例
import { serialize } from 'next-mdx-remote/serialize'
import { MDXRemote } from 'next-mdx-remote'
import Test from '../components/test'
const components = { Test }
export default function TestPage({ source }) {
return (
<div className="wrapper">
<MDXRemote {...source} components={components} />
</div>
)
}
export async function getStaticProps() {
// MDX text - can be from a local file, database, anywhere
const source = 'Some **mdx** text, with a component <Test />'
const mdxSource = await serialize(source)
return { props: { source: mdxSource } }
}
虽然看到这两个人在同一档案中似乎很奇怪,但这是Next.js的一件很酷的事 getStaticProps 财务报告和审定财务报表 TestPage,虽然出现在同一份文件中,但在两个不同的地方运行。 最终您的浏览器捆绑将不包括 getStaticProps 完全,或者它只在服务器上使用的任何功能,所以 serialize 将全部从浏览器捆绑中删除.
重要性: 设置任何
next-mdx-remote编码为单独的“用途”文件。 这样做可能会对Next.js的代码分拆能力造成问题——它必须能够干净地确定仅用于服务器一侧的是什么,以及应该留在客户端捆绑中什么. 如果你放进next-mdx-remote代码输入一个外部的公用设施文件, 某物被打破, 删除它, 然后从上面的简单示例开始, 然后再提交一个问题 .
其他实例
解析前题
Markdown在一般情况下常被前置物配对,通常这意味着在Markdown处理方式上添加一些额外的自定义处理. 为了解决这个问题, next-mdx-remote 带有可选前题解析, 可以通过通过 parseFrontmatter: true 改为 serialize。 。 。 .
是这样的:
import { serialize } from 'next-mdx-remote/serialize'
import { MDXRemote } from 'next-mdx-remote'
import Test from '../components/test'
const components = { Test }
export default function TestPage({ mdxSource }) {
return (
<div className="wrapper">
<h1>{mdxSource.frontmatter.title}</h1>
<MDXRemote {...mdxSource} components={components} />
</div>
)
}
export async function getStaticProps() {
// MDX text - can be from a local file, database, anywhere
const source = `---
title: Test
---
Some **mdx** text, with a component <Test name={frontmatter.title}/>
`
const mdxSource = await serialize(source, { parseFrontmatter: true })
return { props: { mdxSource } }
}
vfile-matter 用于解析前题.
将自定义数据传递给具有“范围”的组件
<MDXRemote /> 接受一个 scope 道具,它使所有值都可用于您的 MDX .
每一键/值对 scope 参数将作为 Javascript 变量被曝光。 所以,举例来说,你可以想象 如果你有一个范围像 { foo: 'bar' },它将被解释为 const foo = 'bar'。 。 。 .
这特别意味着,你需要确保 您的密钥名称 scope 参数是有效的javascript可变名称。 举例来说,通过 { 'my-variable-name': 'bar' } 将生成一个 错误,因为密钥名称不是一个有效的javascript可变名称.
同样重要的是要指出: scope 变量必须作为 参数到组件,它们不能在正文中间显示。 下面的例子说明了这一点.
import { serialize } from 'next-mdx-remote/serialize'
import { MDXRemote } from 'next-mdx-remote'
import Test from '../components/test'
const components = { Test }
const data = { product: 'next' }
export default function TestPage({ source }) {
return (
<div className="wrapper">
<MDXRemote {...source} components={components} scope={data} />
</div>
)
}
export async function getStaticProps() {
// MDX text - can be from a local file, database, anywhere
const source =
'Some **mdx** text, with a component using a scope variable <Test product={product} />'
const mdxSource = await serialize(source)
return { props: { source: mdxSource } }
}
将 " 镜 " 传入 " 序列化 " 功能
您也可以将自定义数据传递到 serialize,然后将数值传递出去,并从结果中提供。 通过传播结果从 source 输入 <MDXRemote />,数据将会提供.
注意,任何范围值均转入 serialize 需要可序列,意思是无法传递函数或组件。 此外,该表所列的任何密钥 scope 参数必须是有效的javascript可变名称。 如果您需要通过不能序列的自定义范围, 您可以通过 scope 直接转到 <MDXRemote /> 在那里它被制造出来。 本节以上有这方面的例子.
import { serialize } from 'next-mdx-remote/serialize'
import { MDXRemote } from 'next-mdx-remote'
import Test from '../components/test'
const components = { Test }
const data = { product: 'next' }
export default function TestPage({ source }) {
return (
<div className="wrapper">
<MDXRemote {...source} components={components} />
</div>
)
}
export async function getStaticProps() {
// MDX text - can be from a local file, database, anywhere
const source =
'Some **mdx** text, with a component <Test product={product} />'
const mdxSource = await serialize(source, { scope: data })
return { props: { source: mdxSource } }
}
自定义组件来自 MDXProvider
如果想要将组件提供给任何 <MDXRemote /> 在申请中,您可以使用 <MDXProvider /> 从 @mdx-js/react。 。 。 .
// pages/_app.jsx
import { MDXProvider } from '@mdx-js/react'
import Test from '../components/test'
const components = { Test }
export default function MyApp({ Component, pageProps }) {
return (
<MDXProvider components={components}>
<Component {...pageProps} />
</MDXProvider>
)
}
// pages/test.jsx
import { serialize } from 'next-mdx-remote/serialize'
import { MDXRemote } from 'next-mdx-remote'
export default function TestPage({ source }) {
return (
<div className="wrapper">
<MDXRemote {...source} />
</div>
)
}
export async function getStaticProps() {
// MDX text - can be from a local file, database, anywhere
const source = 'Some **mdx** text, with a component <Test />'
const mdxSource = await serialize(source)
return { props: { source: mdxSource } }
}
带有点的组件名称( 例如 ). motion.div(中文(简体) )
包含点的组件名称( Q).),如来自 framer-motion,可以与其他自定义组件相同,只需通过 motion 在组件对象中.
import { motion } from 'framer-motion'
import { MDXProvider } from '@mdx-js/react'
import { serialize } from 'next-mdx-remote/serialize'
import { MDXRemote } from 'next-mdx-remote'
export default function TestPage({ source }) {
return (
<div className="wrapper">
<MDXRemote {...source} components={{ motion }} />
</div>
)
}
export async function getStaticProps() {
// MDX text - can be from a local file, database, anywhere
const source = `Some **mdx** text, with a component:
<motion.div animate={{ x: 100 }} />`
const mdxSource = await serialize(source)
return { props: { source: mdxSource } }
}
懒水化
懒水合物会推迟客户端上组件的水合. 这是一种优化技术,可以改进您的应用程序的初始负载,但可能会对您MDX内容中的任何动态内容引入意想不到的交互延迟.
注意: 这将增加一个额外的包 div 绕着你制造的MDX,这是避免 制成时水分不匹配。 。 。 .
import { serialize } from 'next-mdx-remote/serialize'
import { MDXRemote } from 'next-mdx-remote'
import Test from '../components/test'
const components = { Test }
export default function TestPage({ source }) {
return (
<div className="wrapper">
<MDXRemote {...source} components={components} lazy />
</div>
)
}
export async function getStaticProps() {
// MDX text - can be from a local file, database, anywhere
const source = 'Some **mdx** text, with a component <Test />'
const mdxSource = await serialize(source)
return { props: { source: mdxSource } }
}
APIs 密码
这个库揭示了一个功能和一个组件, serialize 财务报告和审定财务报表 <MDXRemote />这两人被故意隔离在他们自己的档案里 serialize 打算运行 服务器侧,所以内部 getStaticProps,它运行在服务器/构建时间. <MDXRemote /> 另一方面,它打算运行在客户端,浏览器中.
-
serialize(source: string, { mdxOptions?: object, scope?: object, parseFrontmatter?: boolean })serialize消耗一串 MDX 。 也可以选择通过以下选项: 直接传递到 MDX,以及可以包含在MDX范围中的范围对象。 函数返回要传递到的对象<MDXRemote />直接说.serialize( // Raw MDX contents as a string '# hello, world', // Optional parameters { // made available to the arguments of any custom MDX component scope: {}, // MDX's available options, see the MDX docs for more info. // https://mdxjs.com/packages/mdx/#compilefile-options mdxOptions: { remarkPlugins: [], rehypePlugins: [], format: 'mdx', }, // Indicates whether or not to parse the frontmatter from the MDX source parseFrontmatter: false, } )访问 https://mdxjs.com/packages/mdx/#编译文件-选项 备查
mdxOptions。 。 。 . -
<MDXRemote compiledSource={string} components?={object} scope?={object} lazy?={boolean} /><MDXRemote />消耗输出serialize以及可选的组件参数。 它的结果可以直接转化为你的组件。 推迟水合 并立即服务 静态标记,通过lazy道具 (英语).<MDXRemote {...source} components={components} />
替换默认组件
渲染将使用 MDXProvider 在引擎盖下。 这意味着您可以用自定义组件来取代 HTML 标记. 这些组件列在MDXJS中 构成部分表。 。 。 .
一个实例是使用您喜欢的样式库来渲染内容 .
import { Typography } from "@material-ui/core";
const components = { Test, h2: (props) => <Typography variant="h2" {...props} /> }
...
如果您愿意, 您也可以将您的整个申请包成 <MDXProvider /> 而不是直接将组件传递到 <MDXRemote />见 实例 见上文.
说明: th/td 由于组件名中的"/"而不能工作.
背景理论( Theory)
在 Next.js app 中装入 MDX 文件的默认方式确实不妙. 之前我们写过 next-mdx-enhanced 用于将您的 MDX 文件制成布局,并导入其前题以创建索引页.
此工作流程来自 next-mdx-enhanced 不过我们取消了一些限制 next-mdx-remote数字 :
- 文件内容必须是本地的 . 您不能将 MDX 文件存储在另一个 repo 、 数据库等中 。 对于一个足够大的操作,最终会把这些作者的内容和那些从事内容展示工作的人分开. 在同一回放中将这两个关切相重叠,使每个人的工作流程更加困难.
- 您必须使用基于文件系统的路由 . 您的页面是根据其位置以 urls 生成的 。 或者你用
exportPathMap,这给作者造成了混淆。 无论如何,移动页面 以任何方式打破事物 - 要么页面的url 或者你的exportPathMap配置 . - 你最终会遇到表演问题. Webpack 是一个 JavaScript 捆绑器,强迫它装入上百/千页的文本内容会吹出你的内存要求. Webpack将每个页面作为一个具有大量元数据的独特对象来存储. 我们的其中一份执行书有几百页 点击超过8GB的内存 需要编辑网站。 建筑耗时超过25分钟.
- 您在构建关系数据时将受到限制 . 将内容组织为动态的,相关的类别在您整个数据结构被前置物质解析为javascript对象并被内存所持有时是困难的.
这么说吧, next-mdx-remote 更改整个模式,以使您不是通过导入而是通过输入来加载您的 MDX 内容 getStaticProps 或者说 getServerProps - 你知道,同样的方式 你将加载其他数据。 库提供工具,以能表现的方式将MDX内容序列化并水分. 这消除了上述所有限制,并以低得多的成本这样做 -- next-mdx-enhanced 是一个非常重的图书馆 有很多自定义逻辑和 一些烦人的限制我们的非正式测试显示,建造时间减少了50%或更多.
自从这个项目最初创建以来 Kent C. Dodds 做了一个类似的项目, mdx-bundler。此库支持在 MDX 文件内导入和导出(只要您手动读取每个导入的文件并传递其内容)并自动处理前题。 如果您有许多文件, 所有导入和使用不同的组件, 您可以从使用中获取好处 mdx-bundler,作为 next-mdx-remote 目前只允许在所有页面中导入和提供组件。 需要注意的是,这个功能成本很高,尽管.. mdx-bundler其输出至少比来自 next-mdx-remote 用于基本标记下的内容.
如何用这个建立博客?
数据显示,99%的开发者工具的使用案例正在建立不必要的复杂的个人博客。 开玩笑的 但说真的,如果你想建立一个博客,供个人或小企业使用,请考虑仅仅使用普通的HTML和CSS. 你绝对不需要使用一个重的全装JavaScript框架来做一个简单的博客. 过几年你回来更新资料的时候 你会感谢自己 还没有10次被解禁.
如果你真的坚持,你看看 下个例例。 。 。
洞穴
环境目标
代码由 next-mdx-remote,用于实际使MDX目标浏览器具有模块支持. 如果您需要支持更老的浏览器,请考虑转录 compiledSource 从 serialize。 。 。 .
import 编号 : export
import 财务报告和审定财务报表 export 无法使用语句 内部 一个MDX文件。 如果您需要使用您的 MDX 文件的组件, 则应将其作为道具提供 <MDXRemote />。 。 。 .
希望这有道理,因为要工作,导入必须相对一个文件路径,这个库允许内容从任何地方加载,而不只是从设定的文件路径加载本地内容. 关于出口,MDX含量被视为数据,而不是 模块,因此我们无法获取可能从MDX中导出的任何值 next-mdx-remote。 。 。 .
警卫
这个库评价客户端的一串JavaScript,这就是它MDXRemotes MDX内容的方式. 将一个字符串评价成javascript,如果不小心进行,可能是一种危险的做法,因为它可以允许XSS攻击. 一定要确保你只通过 mdxSource a. 联合国 serialize 函数改为 <MDXRemote />,如文件指示. 不通过用户输入 <MDXRemote />。 。 。 .
如果您在您的网站上有一个 CSP 拒绝通过 eval 或者说 new Function(),您需要放宽限制,以便使用 next-mdx-remote,可以使用 unsafe-eval。 。 。 .
类型脚本
这个项目确实包括用于TypeScript的本地类型. 两者 serialize 财务报告和审定财务报表 <MDXRemote /> 拥有您所期望的正常类型,库中也会导出一个类型,您可以用该类型键入结果 getStaticProps。 。 。 .
MDXRemoteSerializeResult<TScope = Record<string, unknown>>: 表示返回值serialize编辑TScope通用类型可以传递来表示您通过的范围数据的类型.
下面是TypeScript中简单执行的例子. 您可能不需要以这种方式对 TypeScript 的每个配置执行确切的类型 - 这个示例只是演示这些类型在需要时可以应用的地方 .
import type { GetStaticProps } from 'next'
import { serialize } from 'next-mdx-remote/serialize'
import { MDXRemote, type MDXRemoteSerializeResult } from 'next-mdx-remote'
import ExampleComponent from './example'
const components = { ExampleComponent }
interface Props {
mdxSource: MDXRemoteSerializeResult
}
export default function ExamplePage({ mdxSource }: Props) {
return (
<div>
<MDXRemote {...mdxSource} components={components} />
</div>
)
}
export const getStaticProps: GetStaticProps<{
mdxSource: MDXRemoteSerializeResult
}> = async () => {
const mdxSource = await serialize('some *mdx* content: <ExampleComponent />')
return { props: { mdxSource } }
}
反应服务器组件( RSC) & Next.js app 目录支持
使用时间 next-mdx-remote 在服务器组件内,特别是Next.js的组件内 app 目录,通过从 next-mdx-remote/rsc. 此前,序列化和使步骤是分开的,但是向前推进RSC使得这种分离没有必要.
一些值得注意的差异:
<MDXRemote />现在接受一个source道具,而不是接受序列化输出next-mdx-remote/serialize- 自定义组件无法再通过使用
MDXProvider从上下文@mdx-js/react,因为 RSC 不支持 React 背景 - 通过时访问您 MDX 以外的前题
parseFrontmatter: true,使用compileMdx方法从next-mdx-remote/rsc - 这个
lazy不支持道具, 因为渲染发生在服务器上 <MDXRemote />必须在服务器上提供,因为它现在是一个Async组件。 客户端组件可以作为MDX标记的一部分提供
关于RSC的更多信息,请查看 Next.js 文档。 。 。 .
实例
假设在 Next.js 13+ 应用程序中使用 app 目录.
基础
import { MDXRemote } from 'next-mdx-remote/rsc'
// app/page.js
export default function Home() {
return (
<MDXRemote
source={`# Hello World
This is from Server Components!
`}
/>
)
}
装入状态
import { MDXRemote } from 'next-mdx-remote/rsc'
// app/page.js
export default function Home() {
return (
// Ideally this loading spinner would ensure there is no layout shift,
// this is an example for how to provide such a loading spinner.
// In Next.js you can also use `loading.js` for this.
<Suspense fallback={<>Loading...</>}>
<MDXRemote
source={`# Hello World
This is from Server Components!
`}
/>
</Suspense>
)
}
自定义组件
// components/mdx-remote.js
import { MDXRemote } from 'next-mdx-remote/rsc'
const components = {
h1: (props) => (
<h1 {...props} className="large-text">
{props.children}
</h1>
),
}
export function CustomMDX(props) {
return (
<MDXRemote
{...props}
components={{ ...components, ...(props.components || {}) }}
/>
)
}
// app/page.js
import { CustomMDX } from '../components/mdx-remote'
export default function Home() {
return (
<CustomMDX
// h1 now renders with `large-text` className
source={`# Hello World
This is from Server Components!
`}
/>
)
}
MDX 外部访问前题
// app/page.js
import { compileMDX } from 'next-mdx-remote/rsc'
export default async function Home() {
// Optionally provide a type for your frontmatter object
const { content, frontmatter } = await compileMDX<{ title: string }>({
source: `---
title: RSC Frontmatter Example
---
# Hello World
This is from Server Components!
`,
options: { parseFrontmatter: true },
})
return (
<>
<h1>{frontmatter.title}</h1>
{content}
</>
)
}
替代品
next-mdx-remote 在它所支持的特性上被评出。 如果您需要额外功能,则不由 next-mdx-remote,这里有一些可供考虑的备选方案:
你也许不需要 next-mdx-remote
如果你正在使用 React Server 组件,并且只是尝试使用带有自定义组件的基本MDX,除了核心MDX库之外,你不需要其他任何东西.
import { compile, run } from '@mdx-js/mdx'
import * as runtime from 'react/jsx-runtime'
import ClientComponent from './components/client'
// MDX can be retrieved from anywhere, such as a file or a database.
const mdxSource = `# Hello, world!
<ClientComponent />
`
export default async function Page() {
// Compile the MDX source code to a function body
const code = String(
await compile(mdxSource, { outputFormat: 'function-body' })
)
// You can then either run the code on the server, generating a server
// component, or you can pass the string to a client component for
// final rendering.
// Run the compiled code with the runtime and get the default export
const { default: MDXContent } = await run(code, {
...runtime,
baseUrl: import.meta.url,
})
// Render the MDX content, supplying the ClientComponent as a component
return <MDXContent components={{ ClientComponent }} />
}
您也可以简化此方法 evaluate,如果计划不将编译的字符串传递到数据库或客户端组件,则在单调中编译并运行代码.
import { evaluate } from '@mdx-js/mdx'
import * as runtime from 'react/jsx-runtime'
import ClientComponent from './components/client'
// MDX can be retrieved from anywhere, such as a file or a database.
const mdxSource = `
export const title = "MDX Export Demo";
# Hello, world!
<ClientComponent />
export function MDXDefinedComponent() {
return <p>MDX-defined component</p>;
}
`
export default async function Page() {
// Run the compiled code
const {
default: MDXContent,
MDXDefinedComponent,
...rest
} = await evaluate(mdxSource, runtime)
console.log(rest) // logs { title: 'MDX Export Demo' }
// Render the MDX content, supplying the ClientComponent as a component, and
// the exported MDXDefinedComponent.
return (
<>
<MDXContent components={{ ClientComponent }} />
<MDXDefinedComponent />
</>
)
}
