May 22, 2024

A layman's guide to setting up an MDX blog with Next.js and Tailwind CSS

Background

This blog is written from the perspective of a hardcore Android and IoT engineer who is entering the land of web and browsers while creating my personal website.

So, why not write a blog about setting up the blog?

I have taken most of the content from the Next.js MDX setup guide. The aim if this blog is to cut down the noise and provide the minimal steps for the setup. I have also addressed some configuration problems the documentation for which may require some digging.

The stack

The basics

I have assumed that the base project has been set up. Once done, the following files are the ones that we need to create or edit:

project/
├── src/
│   ├── app/
│   │   ├── blog/
│   │   │   └── (content)/
│   │   │       ├── test-blog/
│   │   │       │   └── page.mdx
│   │   │       └── layout.tsx
│   │   └── globals.css
│   └── mdx-components.tsx
├── next.config.mjs
└── tailwind.config.ts

Start by installing the required basic MDX packages:

npm install @next/mdx @mdx-js/loader @mdx-js/react @types/mdx

Update the next.config.mjs file to use MDX in the project:

import createMDX from "@next/mdx";
 
/** @type {import('next').NextConfig} */
const nextConfig = {
  pageExtensions: ["js", "jsx", "md", "mdx", "ts", "tsx"],
};
 
const withMDX = createMDX();
 
export default withMDX(nextConfig);

Create the mdx-components.tsx file with the following content:

import type { MDXComponents } from 'mdx/types'
 
export function useMDXComponents(components: MDXComponents): MDXComponents {
  return {
    ...components,
  }
}

Now we can create the page.mdx file in the test-blog directory and let the app router do its magic.

Layout and styling

After completing the basic MDX setup, we can notice that even though the content is being rendered, the styling is not does not look good.

To fix this, install the tailwind typography plugin:

npm install -D @tailwindcss/typography

Update the tailwind.config.ts file to include the mdx files and add the typography plugin:

import type { Config } from "tailwindcss";
 
const config = {
  // ...
  content: [
    "./pages/**/*.{js,jsx,md,mdx,ts,tsx}",
    "./components/**/*.{js,jsx,md,mdx,ts,tsx}",
    "./app/**/*.{js,jsx,md,mdx,ts,tsx}",
    "./src/**/*.{js,jsx,md,mdx,ts,tsx}",
  ],
  plugins: [require('@tailwindcss/typography')],
} satisfies Config;
 
export default config;

Now create the layout.tsx file with the following content for the common blog layout:

export default function BlogLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return <div className={"prose m-4 sm:m-8 sm:mx-auto"}>{children}</div>;
}

This fixes the rendering of the different sections, but the text color is still a problem.

To fix this, let's edit tailwind.config.ts again to configure the typography plugin using the colors defined in globals.css:

import type { Config } from "tailwindcss";
 
const config = {
  // ...
  content: [
    "./pages/**/*.{js,jsx,md,mdx,ts,tsx}",
    "./components/**/*.{js,jsx,md,mdx,ts,tsx}",
    "./app/**/*.{js,jsx,md,mdx,ts,tsx}",
    "./src/**/*.{js,jsx,md,mdx,ts,tsx}",
  ],
  plugins: [require('@tailwindcss/typography')],
  theme: {
    extend: {
      typography: {
        DEFAULT: {
          css: {
            "--tw-prose-body": "hsl(var(--foreground))",
            "--tw-prose-headings": "hsl(var(--foreground))",
            "--tw-prose-lead": "hsl(var(--foreground))",
            "--tw-prose-links": "hsl(var(--foreground))",
            "--tw-prose-bold": "hsl(var(--foreground))",
            "--tw-prose-counters": "hsl(var(--foreground))",
            "--tw-prose-bullets": "hsl(var(--foreground))",
            "--tw-prose-hr": "hsl(var(--foreground))",
            "--tw-prose-quotes": "hsl(var(--foreground))",
            "--tw-prose-quote-borders": "hsl(var(--foreground))",
            "--tw-prose-captions": "hsl(var(--foreground))",
            "--tw-prose-kbd": "hsl(var(--foreground))",
            "--tw-prose-kbd-shadows": "hsl(var(--foreground))",
            "--tw-prose-code": "hsl(var(--foreground))",
            "--tw-prose-pre-code": "hsl(var(--foreground))",
            "--tw-prose-pre-bg": "hsl(var(--secondary))",
            "--tw-prose-th-borders": "hsl(var(--foreground))",
            "--tw-prose-td-borders": "hsl(var(--foreground))",
          },
        },
      },
    },
  },
} satisfies Config;
 
export default config;

Code block syntax highlighting

To add syntax highlighting to the code blocks, we will use the remark-gfm and rehype-pretty-code plugins:

npm install remark-gfm rehype-pretty-code

Finally, update the next.config.mjs file to include the plugins:

import createMDX from "@next/mdx";
import remarkGfm from "remark-gfm";
import rehypePrettyCode from "rehype-pretty-code";
 
/** @type {import('next').NextConfig} */
const nextConfig = {
  pageExtensions: ["js", "jsx", "md", "mdx", "ts", "tsx"],
};
 
const withMDX = createMDX({
  options: {
    remarkPlugins: [remarkGfm],
    rehypePlugins: [[rehypePrettyCode, { keepBackground: false }]],
  },
});
 
export default withMDX(nextConfig);

Useful links