Skip to content
Christian Codes

Building a Blog with Next.js, Tailwind, & MDX

Next.js Logo

There's an intimidating number of excellent front-end tools.

Today, there are about 10,000 reasonable approaches for building a blog, which can be daunting. Do I need a a Javascript framework, or is that too heavy-handed? How will I author and store my content? Where will I host it? How do I make it look good?

Let's dive into my answers to these questions.

Choosing a Stack

I've landed on a stack that offers an exceptional developer experience, crazy fast load times, and the flexibility to create rich and interactive client-side experiences.

The trifecta of Next.js, Tailwind, & MDX.

This blog post will break down the "why" of each of these technologies.

Want an example? Check out the source code for this very blog — it's open source!

Why Next.js?

Next.js calls itself "The React Framework for Production," and for good reason.

The React ecosystem is flourishing right now. The library has grown tremendously in the eight (!) years since it was initially released, as has the community surrounding it.

There are so many mature, battle-tested React-based tools on offer. I want to leverage the benefits of that ecosystem. There is no shortage of component-based Javascript frameworks, but React remains the most established.

One could argue that React (or any Javascript framework, for that matter) is too heavy-handed for a simple blog. I would argue that the tools and optimizations are so good now, that there's little reason not to lean on a framework.

Let's talk about that.

What's special about Next.js?

Next.js provides a suite of features & conventions around React that greatly simplify the process of building a web application. Some standouts include:

  • Static generation & server-side rendering
  • File-based routing (make a Javascript file, you've got a page)
  • Zero configuration (no custom Webpack or Babel configs)
  • Built-in client-side routing & image optimization
  • Dead simple serverless API creation
  • ...and more!

Out of the box, React doesn't have an opinion on how your application is structured. Next.js provides sensible defaults that get you out of the business of making decisions about boilerplate. I've written a lot of custom Webpack configs, and it's rarely how I'd like to be spending my time.

Next.js's biggest competitive advantage, in my view, is the way that it approaches server-side rendering. To begin with, every page is pre-rendered. This means that the markup for each page is generated before it ever gets to the user's browser. There's no waiting for React to load and construct the DOM from scratch.

Crucially, it allows for both static generation and server-side rendering on a per-page basis. This is truly the best of both worlds.

For static content (e.g. this blog post), Next.js can statically generate the page at build time, then immediately cache the result for Jamstack-level performance. For dynamic content (e.g. a user's personal settings page), there are several options for dynamically generating the page at request time.

This flexibility makes Next.js appropriate for small static blogs like christiancodes.io and massive dynamic applications like TikTok.

What about other React Frameworks?

I see two major competitors to Next.js in the React ecosystem: Gatsby and Create React App.

Each of these frameworks are useful, but Next.js retains an advantage in several key areas.

Next.js vs Gatsby

Gatsby is a React based static-site generator that leverages GraphQL for sourcing data.

It's a great tool, and I've used it for several projects. There's a rich ecosystem of plugins, and getting a project spun-up is quick and easy.

The major advantage of Next.js over Gatsby is server-side rendering at runtime. Gatsby generates everything at build-time, and can't generate dynamic pages on-the-fly. Dynamic data must be fetched on the client. There are many scenarios where this is inconvenient.

Moreover, Gatsby is heavily reliant on GraphQL. This isn't necessarily a bad thing, but Next.js has the benefit of being agnostic to how the data layer is managed.

Next.js vs Create React App

Create React App is a framework for quickly scaffolding out new React single-page applications. It takes care of boilerplate build configuration and dependencies.

The major advantage of Next.js over Create React App is pre-rendering. Create React App renders everything on the client-side — that time spent parsing Javascript and constructing the DOM can often to lead to a degraded user-experience.

Why Tailwind?

Tailwind CSS is a utility-first CSS framework that moves all styling decisions directly into your markup.

Instead of writing rules like display: flex; and border-radius: 4px, Tailwind exposes utility classes like flex and rounded.

Tailwind also helps to codify your design system — instead of littering your CSS with arbitrary hex color values Tailwind exposes classes like text-gray-500.

I love Tailwind.

Why not just write CSS?

At first, the extra clutter and mental overhead of utility classes seemed like a bad idea. Then I tried it.

In reality, being able to spend all of my time in the markup (JSX) feels really intuitive.

This whole website has essentially zero custom CSS. There's no need to worry about how to scope or architect the CSS. There's no need to worry about the potential runtime overhead of a CSS-in-JS solution.

Everything is a utility class, and making tweaks is dead simple.

Why MDX?

MDX allows you import React components directly inside of Markdown files.

This is a total game changer.

Markdown is by far my favorite way to author blog posts and documentation — it's portable, largely standardized, and a natural fit for Git workflows. But on its own, it's cumbersome to include rich or interactive markup.

MDX combines the ease of writing Markdown with the flexibility of custom React components.

For example: MDX allows me to easily include the next/link and next/image components directly within blog posts, enabling client-side routing and image optimization that would otherwise be unavailable.

I'm also using the excellent @tailwindcss/typography plugin to style the raw markup that MDX generates.

Next.js Integration

There are several ways to integrate MDX with Next.js.

The official @next/mdx plugin is the easiest to configure, and it supports the same file-based routing that comes built into the framework. Just throw a .mdx file in the /pages directory, and you've got yourself a page.

This plugin does have some constraints. Because it treats each MDX file as a normal Javascript module, it can introduce issues with memory utilization at scale. It also requires that files be located in the /pages directory — it's not possible to source the data from anywhere else.

Two libraries, @next-mdx-remote and @mdx-bundler, may serve as good alternatives if these constraints are an issue.

Hosting

There are several excellent free options available for hosting a Next.js application.

I've chosen to host this blog on ▲ Vercel, the makers of Next.js. The developer experience on the Vercel platform is second-to-none — plus, it's free for personal use! Deploy previews are a game changer, and the Git integration is seamless.

Next.js can be deployed on Vercel with no configuration, and the platform automatically handles scaling, analytics, cache invalidation, and API deployment.

Netlify and AWS Amplify are solid alternatives that I've used for several different projects in the past.

Netlify has a similarly strong developer experience, though it cannot claim the same first-party Next.js support held by Vercel.

AWS Amplify, while quite functional, is notably behind both Vercel and Netlify in terms of developer experience.

Wrapping Up

I'm really happy with the Next.js, Tailwind, & MDX stack. If you're working on a project in this space and I can be of assistance, please feel free to reach out!

Additional Useful Libraries

Additional Examples