如何从MongoDB获取博客JSX内容并在NextJS中正常渲染
问题根源
你存入MongoDB的JSX内容本质是普通字符串,React只会在编译阶段把JSX语法转换成React.createElement调用,运行时从数据库拿到的字符串不会被自动解析为可渲染的React元素,所以会直接以原始文本形式展示。同时你现有代码还存在两个明显bug:
getStaticProps没有根据当前slug过滤文章,全量查询后固定取数组第一项,所有路径都会渲染第一篇内容- 重复使用
JSON.parse(JSON.stringify())做序列化,没有正确处理MongoDB的ObjectId类型,还会造成不必要的性能损耗
可行解决方案
优先选择第一种MDX方案,安全性和性能表现更好;如果需要兼容存量已存储的JSX字符串,可以选择第二种运行时解析方案。
方案1:使用MDX存储博客内容(推荐)
MDX是支持嵌入JSX的Markdown格式,Next.js官方有成熟的解析工具链,支持自定义组件映射,你写在className里的Tailwind样式不需要额外配置就能正常生效。
实现步骤
- 安装依赖
npm install next-mdx-remote
- 调整数据库存储格式:将原有content字段的JSX内容改为MDX格式存储即可,原有JSX标签、className属性都可以保留,注意修正Next.js Image组件的错误属性(Image组件需要
src而非href,且必须传alt、width、height)。 - 修改[postslug].js页面代码,修复原有逻辑并加入MDX解析能力:
import Head from 'next/head' import Link from 'next/link' import Image from 'next/image' import { serialize } from 'next-mdx-remote/serialize' import { MDXRemote } from 'next-mdx-remote' import { connectToDatabase } from "../../util/mongodb"; // 注册MDX中用到的自定义组件 const components = { Link, Image } export async function getStaticPaths() { const { db } = await connectToDatabase(); // 只查询需要的slug字段,减少冗余数据传输 const blogs = await db .collection("blogs") .find({}, { projection: { slug: 1, _id: 0 } }) .toArray(); const paths = blogs.map((p) => ({ params: { postslug: p.slug } })); return { paths, fallback: false, }; } export async function getStaticProps(context) { const reqslug = context.params.postslug; const { db } = await connectToDatabase(); // 按slug查询单篇文章,不要全量查询 const post = await db .collection("blogs") .findOne({ slug: reqslug }); // 序列化MDX内容供前端渲染 const mdxSource = await serialize(post.content) return { props: { post: { ...post, _id: post._id.toString(), // 处理MongoDB ObjectId序列化问题 content: mdxSource }, }, }; } const Post = ({post}) => { return ( <> <Head> <title>{post.title}</title> </Head> <article className="max-w-3xl mx-auto py-10 px-4"> <Image src={post.featuredImage} alt={post.alt} width={Number(post.width)} height={Number(post.height)} className="w-full rounded-lg mb-8" /> {/* 渲染MDX内容,传入自定义组件 */} <MDXRemote {...post.content} components={components} /> </article> </> ) } export default Post;
方案2:运行时解析已存储的JSX字符串(兼容存量数据)
如果暂时不想修改存量的JSX字符串数据,可以用react-jsx-parser在运行时解析字符串,注意:该方案仅可解析你自己后台录入的可信内容,绝对不要解析用户提交的内容,否则会存在XSS安全风险。
实现步骤
- 安装依赖
npm install react-jsx-parser
- 修改[postslug].js页面代码:
import Head from 'next/head' import Link from 'next/link' import Image from 'next/image' import JsxParser from 'react-jsx-parser' import { connectToDatabase } from "../../util/mongodb"; export async function getStaticPaths() { const { db } = await connectToDatabase(); const blogs = await db .collection("blogs") .find({}, { projection: { slug: 1, _id: 0 } }) .toArray(); const paths = blogs.map((p) => ({ params: { postslug: p.slug } })); return { paths, fallback: false }; } export async function getStaticProps(context) { const reqslug = context.params.postslug; const { db } = await connectToDatabase(); const post = await db.collection("blogs").findOne({ slug: reqslug }); return { props: { post: { ...post, _id: post._id.toString() } }, }; } const Post = ({post}) => { return ( <> <Head> <title>{post.title}</title> </Head> <article className="max-w-3xl mx-auto py-10 px-4"> <JsxParser // 注入用到的自定义组件 components={{ Link, Image }} // 传入待解析的JSX字符串 jsx={post.content} allowUnknownElements={true} /> </article> </> ) } export default Post;
内容的提问来源于stack exchange,提问作者Big Loot
相关产品推荐
相关产品推荐

