How I built my blog with Next.js, TypeScript, MDX, Tailwind
February 22, 2022
(updated July 23, 2022)
TLDR: In this article, we briefly go over why everyone needs their own blog, their own podcast, their own TV show and their own biography. Then we go over how I built this blog with Next.js, TypeScript, MDX, Tailwind.
This article and this blog are inspired by Josh W. Comeau's article on how he built his blog - his article is a little less of a step-by-step guide but goes over some of the fancy goodies he has built on his blog. It is an interesting read.
Why build your own blog?
First of all, let's address why you would want to have a blog. One of the reasons is that if you are anything like me and aspire to one day become President or some other kind of important person, you absolutely need a blog.
Having a blog will give you a place where you can write about the billion dollar ideas you have and where you can share your favorite Gary Vee and Steve Jobs quotes. It will also impress your mom, and that too is a good thing.
Now, on to how you should build it. There are about many frameworks and SaaS offerings you could use to build your own blog. A lot of startups out there use Ghost. Some also use Webflow. There are a billion more. There also are more programmer-oriented frameworks you could use too. There's Gatsby. There's Hugo. Plenty of options out there.
Let's get started, reinvent the wheel together and impress your mom and your friends (if they exist).
Frameworks and Libraries
Though I enjoy reinventing the wheel, I also enjoy running until my lootcrate weighs 50TB.
Here is a little list of the basic dependencies we are going to be working with:
Next.js is a meta-framework that lets you build isomorphic apps. What that complex word that erudites such as myself know means is that Next.js is a framework on top of the React framework and lets you do fancy things such as Server-Side Rendering (SSR) and Static-Side Generation (SSG). Here's an article that explains what that is all about.
The toolchain can get a little messy when working with React and TypeScript. Fortunately for you, if you are starting your project from scratch, you can use create-next-app to generate your Next.js project in TypeScript directly and it all works out of the box without having to mess with a bunch of configuration files.
To make the website look epic, we are going to be using Tailwind. If you are familiar with Bootstrap, Tailwind is a little similar. The difference is that Tailwind is very close to CSS properties and is not ancient tech akin to the sundial.
The way Tailwind works is it gives you CSS classes like that add corresponding CSS like . It has classes for everything, some of them map directly to CSS - e.g. Tailwind has a class for example - and others are high-level ones, for example, that class I just mentioned which maps to different colors from the Tailwind color palette.
As for the blog post content itself, I write the posts in MDX. MDX is like Markdown except you can embed JSX components in it. You write your MDX, write React code here and there, and then you parse all that text (using one of the many available libraries for that) and you can use the result in your React code.
In MDX, you can also modify how the library converts the MDX elements into React - for example, you could override the way it behaves for large titles and have it render a custom component instead of a plain .
Here's what MDX looks like:
There are a couple of reasons why you might want to use MDX instead of writing a different for each blog post you write. The main one probably being that it lets you truly decouple the content from the more visual aspects of the website.
Using MDX to write your articles
There are several packages you could use to integrate MDX content into your Next.js app:
- next-mdx-enhanced: the README states you should probably not be using it anymore, so I guess maybe don't
- @next/mdx: does not seem to have any types
- mdx-bundler: this one is a bundler and can handle embedded statements
I use next-mdx-remote because it seemed like the easiest to get started with quickly, and I am a busy man (yes). Knowing what I now know, I might have gone with mdx-bundler instead because as its name suggests, it can handle bundling, which would be a nice bonus.
Where to store your MDX
There are a few options when it comes to storing your MDX content. You could stash that MDX content away into a database and pull it from there at runtime or build time (with Next.js's ).
Since we are talking about a blog that will impress your mom here, you can do what I do, which is keep your MDX files in a directory called or (or whatever) at the root of your Next.js project. This comes with the nice bonus that you can commit your articles to GitHub.
Now, if you have a 5,000+ IQ, you might be wondering how I handle metadata for each blog post. For example, with that file, where do I put the metadata such as , , , and other such things.
I store the metadata in a format called YAML Front Matter. That is a YAML-esque format that you can use to write arbitrary structured data at the top of MDX or Markdown files.
You can define whatever key-value in Front Matter. We'll see how to parse that Front Matter header later on in the article.
Here's an example of what the Front Matter data looks like:
Static generation of the articles (prerendering)
Now that you've got your MDX files in that directory, you need to create a that will dynamically render each post.
The way dynamic routes work in Next.js is through naming of the files you put in the directory.
If you have , that will create a dynamic route with as a path parameter. In this case, the route would be . Whatever concrete value you pass as that parameter will then be passed on to your . For example, if somebody loads , the will be made accessible to your component.
Now, since all that MDX blog content is static - which means it is the same on every request regardless of the user and anything else - we can prerender the dynamic pages at build time.
To do that, we need to use two special Next.js functions. The first one is called , which generates every possible value that the route parameter can take on (that is the parameter in ) and it is run at build time.
The second special function is called , which passes props to the at build time. Because you pass in the props at build time and know what the page is going to be like, the page can be prerendered instead of being needlessly rerendered on every request in spite of never changing.
Here's some pseudocode to give you a sense of how you might want to use to prerender your MDX blog posts:
Now Next.js knows all the values that can take on and it will use that information to know what to prerender.
Then we need to use another special Next.js function named , which will, given a , parse the corresponding file content and pass it as a prop to the component. All this happens at build time.
With , you use the s you got from to generate the props you want to pass to your during the static generation:
Then we can use the props we returned from inside our :
That's it, now whenever you run or , Next.js will prerender the pages based on the MDX files you have in your directory.
If this is still a little confusing to you, here's basically what's going on - this is an example in pseudocode of the whole build process, but it is obviously not actual code:
We are done building the dynamic page that renders individual posts. That was already high-tech but buckle up because we are not done yet. We need some sort of page that lists all of our posts and lets us click on whichever one we want to read - at least if we want our blog to be usable. Let's call that an index page.
Here's what we can do here. Using , we can read all the MDX files in the directory, parse the YAML Front Matter metadata and returns an array of all the metadata (and each article ) to our new (the index page that lists all of our posts):
The then receives all that data and renders each post overview:
Running or will now also generate your index page. Now if you want to add some fancier page that can display posts by tag or some other page of the sort, you should be able to do so by simply modifying the logic inside .
My Posts page works this way. In , I group posts by tags and then iterate over the groups to render each article for each tag.
As you might expect, we would need to rework that logic a bit if we needed pagination.
But for now, that's good enough to impress your mom, so I guess we are done!
If you want to see a repository for this - maybe an unstyled version of this blog with everything hooked up, like a basic framework type of thing - do let me know on Twitter or in the comments and I'll consider making one if enough people find it useful.