Jan 12, 2025
Markdown 是一种用于格式化文本的轻量级标记语言。它允许你使用纯文本语法编写,并将其转换为结构有效的 HTML。它常用于编写网站和博客的内容,在Next.js中一般通过MDX
和React-markdown
来实现markdown的渲染
MDX 是 markdown 的超集,它允许你直接在 markdown 文件中编写 JSX。这是一种强大的方式,可以添加动态交互性并在你的内容中嵌入 React 组件。
Next.js 可以支持应用程序内的本地 MDX 内容,以及在服务器上动态获取的远程 MDX 文件。Next.js 插件负责将 markdown 和 React 组件转换为 HTML,包括支持在服务器组件中使用 (在 App Router 中默认使用)。
bashnpm install @next/mdx @mdx-js/loader @mdx-js/react
next.config.mjs
js/** @type {import('next').NextConfig} */ const nextConfig = { // Configure `pageExtensions`` to include MDX files pageExtensions: ["js", "jsx", "mdx", "ts", "tsx"], } const withMDX = createMDX({ extension: /\.mdx?$/, // Add markdown plugins here, as desired options: { remarkPlugins: [remarkFrontmatter], rehypePlugins: [] } }) export default withMDX(nextConfig)
插件在后面介绍
mdx-components.tsx
根目录添加 mdx-components.tsx
文件,全局 MDX 组件,这里可以对markdown中的一些标签进行样式定义,如h1
、h2
等
tsxexport function useMDXComponents(components: MDXComponents): MDXComponents { return { h1: (props) => { return ( <h1> <div id={props.id} className="invisible relative -top-24"></div> <a href={"#" + props.id} id={"#" + props.id}> {props.children} </a> </h1> ) }, …… code: (info) => { const { children } = info const id = Math.random().toString(36).substr(2, 9) if (info["data-language"]) { return ( <div className="not-prose rounded-md border"> <div className="flex h-12 items-center justify-between bg-zinc-100 px-4 dark:bg-zinc-900"> <div className="flex items-center gap-2"> <span className="text-sm text-zinc-600 dark:text-zinc-400"> {/* @ts-ignore */} {info["data-language"]} </span> </div> <CopyButton1 id={id} /> </div> <div className="overflow-x-auto"> <div id={id} className="p-4"> {children} </div> </div> </div> ) } else { return ( <code {...info} className="not-prose rounded bg-gray-100 px-1 dark:bg-zinc-900" > {children} </code> ) } }, ...components } }
PS:其中props.id需要配合rehypeSlug
使用
目录如下:
my-project
├── app
│ └── mdx-page
│ └── layout.tsx
│ └── page.(mdx/md)
|── mdx-components.(tsx/js)
└── package.json
layout.tsx
tsxexport default function MdxLayout({ children }: { children: React.ReactNode }) { return <div className="markdown m-auto max-w-5xl">{children}</div> }
page.mdx
markdown## Vue3 编译宏 ### defineProps - 父子组件传参 ```vue filename="index.js" <template> <div> <Child name="xiaoman"></Child> </div> </template> <script lang="ts" setup> import Child from "./views/child.vue" </script> <style></style> ```
mdx可以直接在 markdown 文件中编写 JSX,所以我们可以在页面直接加入我们自己的组件,比如显示一些基本信息,这里以Frontmatter 组件为例子:
tsxtype FrontmatterProps = { date: string author: string // 其它元数据,如分类、标签、来源、阅读时长等 } export default function Frontmatter({ date, author }: FrontmatterProps) { return ( <div className="my-4 rounded-r border-l-4 border-gray-200 bg-gray-50 px-4 py-3"> {date}{author} </div> ) }
在page.mdx中可以直接加入
markdownimport Frontmatter from "@/components/Frontmatter" <Frontmatter date="2024-02-22" /> ...md内容
目前可完成基本渲染,可以配置一些markdown渲染插件优化整体渲染效果在下文1.3统一介绍
除了本地的md文件,通过外部获取的md可以使用next-mdx-remote
来渲染
bashpnpm install next-mdx-remote
tsximport { CopyButton1 } from "@/components/CopyButton" import { getPostById } from "@/lib/post" import { MDXRemote } from "next-mdx-remote/rsc" export default async function RemoteMdxPage() { const post = await getPostById(3) if (!post) return <div>Post not found</div> const markdown = post.content const mdxOptions = { remarkPlugins:[] rehypePlugins:[] } const components = {} return ( <MDXRemote source={markdown} options={mdxOptions} components={components} /> ) }
MDXRemote
可直接导入使用,source为外部md的字符串格式,这里由于接口直接
其中componets同1.3中的一致,可设置标签渲染样式等
mdxOptions包含两类配置remarkPlugins
、rehypePlugins
,在1.3中统一介绍
完成md的基础页面渲染后,需要两类插件remarkPlugins
、rehypePlugins
来辅助实现代码高亮
、表格优化
等
插件列表:
插件名称 | 插件类别 | 插件功能 |
---|---|---|
remark-gfm | remarkPlugins | 支持 GitHub Flavored Markdown 语法,如表格、任务列表、删除线等扩展功能 |
rehype-slug | rehypePlugins | 为 Markdown 中的标题(h1-h6)自动生成 id 属性,便于创建锚点链接和目录 |
rehype-pretty-code | rehypePlugins | 提供代码块美化功能,支持行号、高亮特定行、主题定制等 |
@jsdevtools/rehype-toc | rehypePlugins | 自动生成markdown目录 |
这里主要介绍下**rehype-pretty-code
以及@jsdevtools/rehype-toc
**,根据需求自定义配置
rehype-pretty-code
是一款由shiki
语法高亮器驱动的 Rehype 插件,可为 Markdown 或 MDX 提供漂亮的代码块。它既可以在构建时在服务器上工作(避免运行时语法高亮),也可以在客户端上进行动态高亮。
安装
bashnpm install rehype-pretty-code shiki
放在rehypePlugins
配置中
tsrehypePlugins: [ () => (tree) => { visit(tree, (node) => { if (node?.type === "element" && node?.tagName === "pre") { const [codeEl] = node.children if (codeEl.tagName !== "code") return node.raw = codeEl.children?.[0].value } }) }, [ rehypePrettyCode, { theme: "material-theme-lighter" } ], () => (tree) => { visit(tree, (node) => { if (node?.type === "element") { if (!("data-rehype-pretty-code-fragment" in node.properties)) { return } for (const child of node.children) { if (child.tagName === "pre") { child.properties["raw"] = node.raw } } } }) } ]
对插件配置的解释:
代码高亮处理流程是:
第一段代码提取并保存原始代码
第二段rehype-pretty-code 处理代码高亮
第三段代码将原始代码保存到高亮后的结构中
这样做是因为代码高亮处理会改变 DOM 结构,所以需要在处理前保存原始代码,然后在处理后重新附加到新的结构上。
@jsdevtools/rehype-toc
可以根据markdown标签直接生成目录
安装:
bashpnpm install @jsdevtools/rehype-toc
用法:
tsxrehypePlugins: [ ...config [ //@ts-ignore toc, { headings: ["h1", "h2", "h3", "h4", "h5"], customizeTOC: (tocAll: any) => { data = tocAll return false } } ] ]
根据heading会生成一个目录树通过customizeTOC,通过目录树自行创建目录组件markdown-nav.tsx
tsx"use client" import clsx from "clsx" import { useEffect, useState } from "react" export default function MarkdownNav(props: any) { switch (props.tagName) { case "nav": { return ( <nav {...props.properties}> {props.children.map((item: any, index: number) => { return <MarkdownNav {...item} key={index} /> })} </nav> ) } case "ol": { return ( <ol {...props.properties}> {props.children.map((item: any, index: number) => { return <MarkdownNav {...item} key={index} /> })} </ol> ) } case "li": { return ( <li {...props.properties}> {props.children.map((item: any, index: number) => { return <MarkdownNav {...item} key={index} /> })} </li> ) } case "a": { return ( <> <a {...props.properties} > {props.children.map((item: any, index: number) => { return <MarkdownNav {...item} key={index} /> })} </a> </> ) } default: return <>{props.value}</> } }
除了官方提供的mdx渲染以外,还可以使用react-markdown
来渲染
这里简单介绍一个使用例子作为了解,使用还是以官方MDX优先
bashpnpm install react-markdown
tsximport { type Post } from "@/lib/post" import ReactMarkdown from "react-markdown" import rehypeHighlight from "rehype-highlight" import rehypeRaw from "rehype-raw" import rehypeSlug from "rehype-slug" import remarkGfm from "remark-gfm" export default function ReactMarkdownCom({ post }: { post: Post }) { return ( <ReactMarkdown rehypePlugins={[rehypeHighlight, rehypeSlug, rehypeRaw, remarkGfm]} components={} > {post.content} </ReactMarkdown> ) }
用法与React
一致,插件与1.3的使用方法相同
⚠️**rehype-pretty-code
**不可在ReactMarkdown中使用,有兼容性问题,可采用rehype-highlight
或者react-syntax-highlighter
来实现代码高亮
通过使用 MDX 渲染 和 React-markdown 渲染,开发者可以在 Next.js 中优雅地渲染 Markdown 文件,满足不同需求。MDX 提供了更强大的动态渲染能力,适合复杂的页面需求;而 React-markdown 则提供了简单且高效的解决方案,适用于较为静态的内容展示。结合各类插件,开发者可以进一步提升渲染效果,优化用户体验,打造更加优雅和高效的 Markdown 渲染方案。