Managing Markdown content doesn't have to be hard!
I've created this project starter that uses MDX to power your Next.js content only using next-mdx-remote
and @tailwindcss/typography
packages.
NOTHING else is required, but nice to have are these plugins for your parser: rehype-autolink-headings
, rehype-prism-plus
and rehype-slug
to make your life easier (more on these later on).
Here's the link to the GitHub repo and here's the live demo.
Don't forget to visit the demo's Blog page and view the sample entries. They are full-written articles also featured on my website.
First let's review what I'm including in this project starter:
- Next.js setup for TypeScript and TailwindCSS
- Layout (for navs and footers) and Meta (for dynamic SEO) components
- Project directory for storing local blog files
- Utility functions to parse your blog content
How to use
- From your terminal clone the repo,
- Navigate to the project's directory and install all its npm dependencies
$ git clone https://github.com/ekqt/mdx-project-starter.git <FOLDER_DIRECTORY>
$ cd <FOLDER_DIRECTORY>
$ npm install
That's all you need. You can change the <Meta />
and remove the GitHubCorner
component to your own and write your own content in the /content
and /blog
directories, everything else is already set.
How does it work?
The magic happens within the /util
folder. getMdx
and getPaths
. They both illustrate how we can use next-mdx-remote
to process our MDX files in a Node.js context to statically serve our content. Keep in mind this is the ONLY required package for this. All the others after it are just to jazz up our content.
Let's break down each of these util functions, starting with getMdx
:
import fs from "fs";
import path from "path";
import { serialize } from "next-mdx-remote/serialize";
import rehypeSlug from "rehype-slug";
import rehypeAutolinkHeadings from "rehype-autolink-headings";
import rehypePrism from "rehype-prism-plus";
- We are using
fs
from Node.js which enables us to interact with the file system. The fs.readFileSync method returns the files content. - Similarly,
path
allows us to work with files and directory paths, the path.joins() method joins all the given segments. serialize
runs on the server-side or/and at build time to generate an object that can be passed directly into our frontend<MDXRemote />
component.- Then we are left with the
rehypePlugins
which ARE NOT REQUIRED but as an added bonus helps us do the following:rehypeSlug
generates ids to all headings that do not yet have one.rehypeAutolinkHeadings
looks through all headings that have ids and injects a link to them.rehypePrism
provides classes to your code for syntax highlighting and line numbers functionalities (does require additional CSS).
How do we use all of these packages?
The function takes two parameters: (a) the directory's path you are targeting and (b) the slug or filename you want to read. Then it goes ahead and gets the file's content and parses it with the options provided. Here's how the function looks:
export default async function getMdx(dirPath: string, slug: string) {
const source = fs.readFileSync(path.join(dirPath, slug + ".mdx"), "utf8");
return await serialize(source, {
parseFrontmatter: true,
mdxOptions: {
rehypePlugins: [
rehypeSlug,
rehypePrism,
[
rehypeAutolinkHeadings,
{
properties: {
className: ["anchor"],
},
},
],
],
format: "mdx",
},
});
}
How do we render it on the client-side?
It's all downhill from here. You just need to call the util getMdx
from the server side and pass it as a prop.
import { MDXRemote } from "next-mdx-remote";
export async function getStaticProps() {
const mdxSource = await getMdx("content", "index");
return { props: { source: mdxSource } };
}
export default Home ({ source }){
return(
<article className='prose'>
<MDXRemote {...source} />
</article>
)
}
Notice a couple of things:
- Having the utility function really cleans up your code. You can choose to use Markdown files to power content blocks from any given page OR generate an entire page (such as a blog post) with it too.
- TailwindCSS comes into play here, by installing the pluging
@tailwindcss/typography
you have access to theprose
class name, which formats the entire content more information on it, here.
Generating your project's paths
Now changing gears let's review how to generate your project's paths using the getPaths
function:
import fs from "fs";
import path from "path";
export default function getPaths(dirPath: string) {
const files = fs.readdirSync(path.join(dirPath));
return files.map((file) => ({
params: {
slug: file.replace(".mdx", ""),
},
}));
}
Once again, we use the same Node.js functions and provide the paths for our getStaticPaths()
data fetching function.
Conclusion
Powering your project's content with MDX cannot be any easier. There are multiple solutions and libraries out there for this, however. I've found that this is the most flexible solution I've been able to come up with. Clone the GitHub repo and experiment with it to create your own solutions. A couple of final thoughts:
- Using the additional libraries to enable the plugins aren't requireq but a great addition for your content
- Syntax highlighting is subject to configuring CSS classess to a desired theme
- If your MDX files have custom component you would need to map those components when invoking
<MDXRemote />
from the client-side - With this setup you cannot use
.mdx
as standalone pages
Thanks for reading!