remark-mdx-frontmatter-nextjs v1.1.2
remark-mdx-frontmatter-nextjs
What is it?
Yet another plugin to parse frontmatter data and expose it to the crowd. I know there's a much more popular remark-mdx-frontmatter out there, but i couldn't make it do what I wanted. I'm preparing a PR on the side and hopefully this package will be soon archived.
How to use it?
npm i -S remark-mdx-frontmatter-nextjs
In your
next.config.mjs
, add:import withMdx from "@next/mdx"; import remarkFrontmatter from "remark-frontmatter"; import remarkMdxFrontmatter from "remark-mdx-frontmatter-nextjs"; const confMdx = withMdx({ extension: /\.mdx$/, options: { remarkPlugins: [ remarkFrontmatter, remarkMdxFrontmatter, ], rehypePlugins: [], }, }); export default confMdx({ pageExtensions: ["mdx"], });
By default, the frontmatter metadata will be passed in
pageProps
, so that you can catch them in_app
and usenext/head
directly there. Here's my own, in case you want some boilerplateimport '../styles/globals.css' // https://stackoverflow.com/a/67464299/335243 import type { AppProps as NextAppProps } from "next/app"; import Head from 'next/head' type AppProps<P = any> = { pageProps: P; } & Omit<NextAppProps<P>, "pageProps">; // from MDX type PageMeta = { title?: string; description?: string; timestamp?: number; tags?: string[]; layout?: boolean; }; const AUTHOR = 'Julien Barbay' const HANDLE = '@y_nk' export default function App({ Component, router, pageProps }: AppProps<PageMeta>) { const { pathname } = router; const { title, description, tags, timestamp } = pageProps; return ( <> <Head> <title>{title}</title> <meta name="author" content={AUTHOR} /> <meta name="description" content={description} /> {tags && <meta name="keywords" content={tags.join(",")} />} <meta name="twitter:creator" content={HANDLE} /> <meta name="twitter:card" content="summary" /> <meta property="article:author" content={AUTHOR} /> <meta property="article:published_time" content={`${timestamp}`} /> <meta property="og:title" content={title} /> <meta property="og:type" content="article" /> <meta property="og:url" content={pathname} /> <meta property="og:description" content={description} /> </Head> <Component {...pageProps} /> </> ); }
Customisation
The plugin allows ONE option to help inject your frontmatter object into your NextJS application. The default renderer is:
const defaultRenderer = (data, node) => {
return `
export const getStaticProps = async () => {
return { props: ${JSON.stringify(data)} }
}
`;
};
As you can guess, that's why the frontmatter data are coming back as pageProps
in your _app
now.
If you already use getStaticProps
in your mdx it is obvious that you'll have a problem of duplicate declaration, but there's a plan B: you can pass another renderer of your choice instead. A suitable example can be:
const customRenderer = (data, node) => {
return `MDXContent.frontmatterData = ${JSON.stringify(data)}`
};
And then in your app, you'll have to catch your data like this:
import '../styles/globals.css'
export default function App({ Component, pageProps }) {
const { frontmatterData } = Component
return (
<div>
<Component {...pageProps} />
</div>
);
}
More about the package
Why another package?
I just arrived in the NextJS game and I wanted to make a simple blog with mdx support and use frontmatter data to decorate my pages with next/head
, yet I didn't want to write an overly verbose header everytime I write a blog post. Layouts are supposed to be for that reason, right?
The existing solutions
There's next-mdx-remote but it doesn't allow imports within your mdx file, which was a no-go for me.
There's next-mdx-enhanced but the repo itself says you shouldn't use it because there's issue at scale (and it's way too opinionated anyway)
There's also mdx-bundler but after reading quick hands on, i got scared - the dependency to esbuild seems a bit overkill to what i wanted to achieve
The only other way to load mdx is to use
@next/mdx
but there's no built-in support for frontmatter, you gotta use remark plugins for that. Just using the remark-frontmatter will simply strip out the metadata from the page, but the values will be lost. Finally, there's remark-mdx-frontmatter but it does not allow to customise the exports smoothly enough to exploit them in NextJS.
I came to the conclusion that remark-mdx-frontmatter
was too narrow to be useful, yet it was a perfect base for me to tinker (thank you).