You are on page 1of 5

How to Build Your Own Blog with Next.

js and MDX
When I decided to build my blog, I found many tools out there that were readily available. I
looked at Gastby along with content management systems like Ghost, Contentful, Sanity dot io,
and HUGO.

But I needed something that I could have total control over. I've also always been someone who
loves the flexibility of writing own my custom code. When I do this, I can conveniently go back
to where any issues might be when a problem arises.

Gatsby provides this flexibility, and it is something I could get familiar with pretty easily since
it's built on a library I use every day (React.js). But, I found out that I can do the exact same
thing with Next.js by integrating MDX.

"What is MDX?" You might ask me.

Well... MDX is more or less like the markdown files we always see in GitHub repositories.
MDX brings this flexibility into a markdown file by allowing you to literally write or import
JavaScript (React) components into your articles. This in turn saves you from writing repetitive
code.

In this article, I am going to show you how I built my blog with these tools, so you can also try
building something similar. You'll like this simple stack if you are a person who loves the
flexibility that this approach brings.

So, sit tight, and let's get started.

How to Start Building – My Trial and Error


To build a blog with Next.js and MDX, there are four popular options that you can choose from.

They are:

 @next/mdx, which is the official tool built by the Next.js team


 Kent C. Dodds' mdx-bundler
 next-mdx-remote, which is a tool built by the Hashicorp team
 next-mdx-enhanced, which is a tool also built by Hashicorp (I honestly don't know why
they decided to build two)

At first, I started by using Kent's mdx-bundler, but then I ran into a lot of problems with the tool.
It is a library that is based on the new ECMAScript standards that allow us to create ESModules
in the browser, and I was using a very old version of Next.js (V10.1.3, my bad honestly, I didn't
know any better).
I did a lot of downgrading and upgrading of Next.js to fix this problem to no avail. There was a
certain error that stuck with me, and refused to go away for days. Yes, for days! I felt like crying
during that period. Take a look at the error below:

module not found: can't resolve 'builtin-modules'

Apparently, for mdx-bundler to work, it needs another npm package called esbuild to do the
necessary compiling processes that work under the hood.

npm i mdx-bundler esbuild

Luckily for me — at least I thought I was lucky — Cody Brunner submitted an issue about this
particular error. Going through the discussions on the issue, a lot of possible fixes were
suggested, some of them were related to Webpack, modifying your next.config.js file, and
whatnot.

module.exports = {
future: {
// Opt-in to webpack@5
webpack5: true,
},
reactStrictMode: true,
webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
if (!isServer) {
// https://github.com/vercel/next.js/issues/7755
config.resolve = {
...config.resolve,
fallback: {
...config.resolve.fallback,
child_process: false,
fs: false,
'builtin-modules': false,
worker_threads: false,
},
}
}

return config
},
}

In the snippet above, it shows that Webpack5 was still a feature that was in progress for Next.js –
hence the snippet below in the config:

future: {
webpack5: true
}

But, now the latest version of Next.js supports Webpack5 by default, so there's no need to add
that object — if it works for you — in the config.
After going through the discussions, I found a comment (by Kent) that says running npm update
would fix the issue, and it did work for Cody Brunner. But not for me apparently.

When I couldn't find a possible fix to this error, I decided to use next-mdx-remote, and the only
issue I faced was the breaking change that was added to the tool. Before version 3 of next-mdx-
remote you would normally render parsed markdown content by doing the following:

import renderToString from 'next-mdx-remote/render-to-string'


import hydrate from 'next-mdx-remote/hydrate'
import Test from '../components/test'

export default function TestPage({ source }) {


const content = hydrate(source, { components })

return <div className="content">{content}</div>


}

export async function getStaticProps() {


// MDX text - can be from a local file, database, anywhere
const source = 'Some **mdx** text, with a component <Test />'
const mdxSource = await renderToString(source, { components })

return {
props: {
source: mdxSource,
},
}
}

The breaking change that was added in version 3 of the package stripped off a lot of internal
code that was perceived to cause poor experiences for people who were using it at that time.

The team went on to announce the reason behind this change and the major changes. Take a look
at them below.

This release includes a full rewrite of the internals of next-mdx-remote to make it faster, lighter-
weight, and behave more predictably! The migration should be fairly quick for most use-cases,
but it will require some manual changes. Thanks to our community for testing out this release
and providing early feedback. heart.

Major changes to next-mdx-remote:

 renderToString has been replaced with serialize


 hydrate has been replaced with <MDXRemote />
 Removed provider configuration. React context usage should now work
without additional effort.
 Content will now hydrate immediately by default
 Dropped support for IE11 by default

With this new change, the previous implementation will now become:
import { serialize } from 'next-mdx-remote/serialize'
import { MDXRemote } from 'next-mdx-remote'

import { Test, Image, CodeBlock } from '../components/'

const components = { Test }

export default function TestPage({ source }) {


return (
<div className="content">
<MDXRemote {...source} components={{ Test, Image, CodeBlock }} />
</div>
)
}

export async function getStaticProps() {


// MDX text - can be from a local file, database, anywhere
const source = 'Some **mdx** text, with a component <Test />'
const mdxSource = await serialize(source)

return {
props: {
source: mdxSource,
},
}
}

How to Build the Blog


In the previous section, I walked you through some of the issues I encountered while I was
choosing a suitable tool to use.

In this section, we're going to cover how you can build a similar blog like mine.

We'll start by creating a Next.js app with the command below:

npx create-next-app blog

The command above will give you a boilerplate of a typical Next.js app. For the sake of brevity,
I'll be focusing more on the pages and src/utils folders of this app.

|--pages
| |-- blog
| | |-- index.js
| | |-- [slug].js
| |-- _app.js
| |-- index.js
|--src
| |-- utils
| |-- mdx.js
|--data
| |-- articles
| |-- example-post.mdx
| |-- example-post2.mdx

In a typical blog, we'd need to write blog posts or articles. In this blog, we're using markdown
(MDX) to write our articles, which is why you can see that we have two .mdx files inside the
data/articles directory. You can have more than that, as far as the number of articles you
want to write goes.

You might also like