next/mdx vs mdx-remote vs bundle/mdx
前言
學習 Next.Js,最容易看到的範例就是做一個 BLOG 了!
為了簡化開發方式,Next.Js 本身提供了對 md 的支援
但又為了讓他可以使用 React 組件,採用 mdx 格式 ── 就是 md + 簡單的 js
若只是當練手使用 Next.Js,確實會覺得這樣的支援沒什麼用處,畢竟大部份資料多半來源於 DB
但若是只要做幾個特定頁面,而有大量的靜態資料時,Next.Js 這樣的搭配方式,可以很輕易的解決問題!
而正巧手邊有這樣的需求,籍由 MDX,很輕鬆地透過 SSGs(Static Site Generators)完成所有頁面
僅須針對少數頁面額外實作就好!
若正巧有類似需求,去爬文會發現主要有 3 種解決方案!
以下列舉各自差異供參考
@next/mdx
即 Next.Js 官方提供的解法
由於 Next.Js 是 file-base route,可以直接將 mdx 檔置於pages
路徑下,搭配此套件後
即可自動將 mdx 渲染成正常的 html 頁面
也最為簡單,安裝完套件,在next.config.js
配置完,就直接生效了
只需簡單寫一個layout
補上 tag 要搭配的 style 就可以
比如每個 TAG 都改由 MUI Library 渲染,或是有自己寫的 css
且也可以直接增加 React 組件,達到靈活調整頁面的目的
美中不足的小缺點就是
預設不支援 parse frontmatter,須另安裝套件gray-matter
因實務上不可能完全無樣式,所以mdx-layout.tsx
是必寫的
在這裡多寫個getStaticsProps
,透過gray-matter
簡單寫個小邏輯來parse就可以了
程式碼整體來說還算乾淨
mdx 頁面內容大約如下
# 大標題
內容
## 二標題
`code`
import MyOwnComponent from "../components/my-own-component";
<MyOwnComponent />
import MdxLayout from '../components/layout/mdx-layout';
export default ({ children }) => <MdxLayout type={'PWA'}>{children}</MdxLayout>
mdx-layout
import React from 'react';
import { MDXProvider } from '@mdx-js/react';
import { MDXComponents } from "mdx/types";
import { Divider, Typography } from "@mui/material";
const components: MDXComponents = {
code: (props) => <code style={{ backgroundColor: "#e1fde2" }} {...props} />,
hr: (props) => <Divider sx={{ mt: 4, mb: 4 }} />,
h1: (props) => (
<Typography variant="h1" component={"h1"} gutterBottom mt={2}>
{props.children}
</Typography>
),
h2: (props) => (
<Typography variant="h2" component={"h2"} gutterBottom mt={2}>
{props.children}
</Typography>
),
p: (props) => <Typography variant="body1">{props.children}</Typography>,
};
export default function MdxLayout({type, children}: MdxLayoutProps) {
return (
<MDXProvider components={components}>
{children}
</MDXProvider>
);
}
next-mdx-remote
從套件名可直接看出,遠端取得 md 或 mdx 檔
不論來源是 DB 亦或是像 HEXO 這種 BLOG 產生器,即專案自己的特定目錄(如_POSTS
)都可以
一般為了搭配SSGs,即純靜態頁面,通常就直接是專案目錄了
主張的就是資料來源與 Next.Js 解耦,雖然大部份人應該都還是放在一起 XD
用法也跟差不多
都是需要寫getStaticProps()
,而此套件直接就支援front-matter了,只要多個參數就可以 parse
bundle/mdx
跟next-mdx-remote
相似,是封裝比較完整的framework,不受限於 Nex.Js,可以在任何框架搭配使用
基本上優缺點也跟next-mdx-remote
一樣,亦可直接提供參數自動取得 front matter
以在 Next.Js 使用來說,next-mdx-remote
跟 bundle/mdx
基本上可說是等價
front matter 會遇到的一個坑
當front matter 有使用日期時
會出現下列錯誤訊息
Reason:
object
(“[object Date]”) cannot be serialized as JSON. Please only return JSON serializable data types.
須包覆一層JSON.parse(JSON.stringify())
如下範例
import { serialize } from 'next-mdx-remote/serialize';
export async function getStaticProps() {
const mdx = `---
author: John Doe
date: 2022-08-18
---
# Hello World
Here's a component used inside Markdown:
<Hello />`;
const mdxSource = await serialize(mdx, {
parseFrontmatter: true,
});
return {
props: {
source: JSON.parse(JSON.stringify(mdxSource)),
},
};
}
後記
3 個方式都快速試一遍後,仔細想想我的需求
解耦MD檔案
與Next.Js
固然好
若希望將md檔名
與文章實際網址
拆分
以 hexo blog 的作法,就是在 front matter 增加parmalink
來取得內容
會發現還需要額外寫getStaticPaths()
產路由邏輯且控制好產出檔案到指定目錄
寫路由簡單
但為了方便比對實體檔案與路由的路徑對照
勢必需要增加一份db.json
(也是 hexo 的作法)做參照,以便可以快速比對
發覺繼續做下去,其實就是重新發明 HEXO 了!
再回過頭來看了一下我的靜態資料
其實就是現有 md 檔案
副檔名改名成mdx
、尾端加上export
就可以了
export default ({ children }) => <MdxLayout>{children}</MdxLayout>
md檔案
異動機率趨近於 0 ,直接把少數中文檔名改成對應url英文
,放置於pages
剩下的再寫個小腳本快速跑一下就結束的事
最後就直接採用原生的@next/mdx
作法
雖然讓md檔
稍醜了些,但換來的好處是
我不必逐一將每一頁面轉成 React 組件,重新刻一輪 html tag
只要略寫一些MDXComponents
代上我要的樣式就收工了!
若真的靜態內容要異動,md 帶來的可讀性,還是遠勝於 html 的!
參考資料
How to Set Up MDX in Next.js
Guide to Using Mdx-bundler With Next.js