This content originally appeared on DEV Community and was authored by gerry leo nugroho
Why stress over complicated setups when you can use tools that make blogging smooth and fun? Next.js
, MDX
, and Tailwind CSS
are a dream team for building a blog that’s both powerful and stylish at the same time. Next.js handles the backend and routing, MDX lets you create dynamic and interactive posts, and Tailwind CSS gives you a clean, customizable design system.
Together, they let you focus on creating content while keeping your blog fast, flexible, and visually appealing. Plus, features like clean typography and light/dark mode options aren’t just nice-to-haves—they make your blog more engaging and user-friendly. Whether you're just starting out or planning to grow, this stack has you covered. Ready to get started? Let’s build something awesome.
1. Why This Tech Stack?
Picking the right tools is crucial for building a great blog. The combo of Next.js, MDX, and Tailwind CSS delivers a smooth, high-performance, and flexible setup. Here’s why it works:
- Next.js: The Backbone of Your Blog
Next.js simplifies web development with server-side rendering (SSR) and static site generation (SSG), ensuring fast load times and better SEO. Its file-based routing makes content organization a breeze, and built-in optimizations let you focus on content, not performance.
- MDX: Where Content Meets Interactivity
MDX enhances Markdown by letting you embed React components directly into posts. Add interactive charts, buttons, or custom elements effortlessly, making your blog both informative and engaging.
- Tailwind CSS: Design Without Limits
Tailwind CSS uses a utility-first approach, enabling quick, beautiful designs. Style elements directly in your markup—whether it’s clean typography or dark mode. Its theming keeps your blog consistent and uniquely yours.
- Focus on Content, Not Complexity
This stack lets you focus on content. Next.js handles performance, MDX enables dynamic posts, and Tailwind ensures a polished design. Together, they create a fast, flexible, and future-proof blog.
2. Setting Up Your Development Environment
Before diving into the exciting part of building your blog, let’s make sure your development environment is ready to go. This section will guide you through setting up the foundational tools and dependencies needed to kickstart your project. Don’t worry—it’s easier than it sounds!
Step 1: Install Node.js and a Package Manager
To get started, you’ll need Node.js, which includes npm
(Node Package Manager) by default. Alternatively, you can use Yarn or pnpm if you prefer a faster or more efficient package manager.
-
Install Node.js: Head over to the official Node.js website and download the latest stable version. During installation, ensure that
npm
is also installed. - Optional: If you’d rather use Yarn or pnpm, install them globally using the following commands:
npm install -g yarn # For Yarn
npm install -g pnpm # For pnpm
Once installed, verify your setup by running:
node -v && npm -v
This should display the versions of Node.js and npm (or your chosen package manager).
Step 2: Create a New Next.js Project
With Node.js in place, it’s time to scaffold a new Next.js project. Open your terminal and run the following command:
npx create-next-app@latest my-blog
Replace my-blog
with your desired project name. You’ll be prompted with a few questions about TypeScript, ESLint, and other configurations. For now, feel free to accept the defaults or customize based on your preferences.
Once the setup is complete, navigate into your project directory:
cd my-blog
At this point, you can test your new Next.js app by running:
npm run dev
Visit http://localhost:3000
in your browser, and you should see the default Next.js welcome page. Success!
Step 3: Add Dependencies for MDX and Tailwind CSS
Now that your Next.js project is up and running, let’s add the necessary dependencies for MDX and Tailwind CSS.
- Install MDX Support
To enable MDX in your project, install the required packages:
npm install @next/mdx @mdx-js/loader
These packages allow Next.js to process .mdx
files seamlessly.
- Install Tailwind CSS
Tailwind CSS requires a bit more setup, but don’t worry—it’s straightforward. Run the following command to install Tailwind and its peer dependencies:
npm install tailwindcss postcss autoprefixer
Next, initialize Tailwind’s configuration files:
npx tailwindcss init
This will generate two files: tailwind.config.js
and postcss.config.js
. We’ll configure these later.
Step 4: Verify Your Setup
Your project now has all the core dependencies installed. Here’s a quick recap of what we’ve done so far:
- Installed Node.js and a package manager.
- Created a new Next.js project.
- Added support for MDX and Tailwind CSS.
You’re ready to move on to the next steps, where we’ll configure MDX and Tailwind CSS to work together harmoniously. But before that, take a moment to commit your initial setup to Git (if you’re using version control):
git init
git add .
git commit -m "Initial setup with Next.js, MDX, and Tailwind CSS"
With the foundation laid, you’re well on your way to building a modern, feature-rich blog. Let’s keep going!
4. Configuring MDX for Dynamic Content
Now that your project is set up, let’s dive into configuring MDX to unlock its full potential. MDX allows you to combine the simplicity of Markdown with the power of React components, enabling you to create dynamic and interactive blog posts. Here’s how to get started.
Step 1: Configure next.config.js
for MDX Support
To enable .mdx
files in your Next.js app, you’ll need to update the next.config.js
file. Open the file and modify it to include the following configuration:
const withMDX = require('@next/mdx')({
extension: /\.mdx?$/,
})
module.exports = withMDX({
pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'md', 'mdx'],
})
This setup tells Next.js to treat .mdx
files as valid pages and components. Save the file, and your app is now ready to process MDX content.
Step 2: Create an Example Blog Post in MDX Format
With MDX configured, let’s create your first blog post. Inside the pages
directory, create a new folder called posts
to organize your content. Then, add a file named my-first-post.mdx
with the following content:
---
title: "My First Blog Post"
date: "2023-10-01"
---
# Welcome to My Blog
This is my very first post written in **MDX**! MDX allows you to write Markdown while embedding React components for dynamic content.
Here’s an example of a styled button:
<Button>Click Me!</Button>
And here’s some code for good measure:
`console.log("Hello, world!")`
Isn’t this amazing? You can mix static content with interactive elements effortlessly.
Notice how we’ve embedded a <Button>
component directly in the Markdown. This is where MDX truly shines—it lets you bring your content to life.
Step 3: Embed React Components Within Markdown
To make the <Button>
component work, you’ll need to define it. Create a new folder called components
in the root of your project, and inside it, add a file named Button.js
with the following code:
export default function Button({ children }) {
return (
<button className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">
{children}
</button>
)
}
Next, import the Button
component into your MDX file by adding this line at the top of my-first-post.mdx
:
import Button from '../../components/Button'
Now, when you run your app and navigate to /posts/my-first-post
, you’ll see your blog post rendered with a fully functional button. This approach opens up endless possibilities—embed charts, forms, or even custom widgets to enhance your content.
Why Embedding React Components Matters
The ability to embed React components within Markdown is a game-changer. Imagine creating tutorials where readers can interact with live code examples, or embedding visually appealing charts to illustrate data points. With MDX, your blog becomes more than just a collection of static pages—it’s an interactive experience that keeps readers engaged.
By combining Markdown’s simplicity with React’s flexibility, MDX empowers you to craft content that’s both informative and dynamic. Whether you’re writing technical guides, sharing personal stories, or showcasing projects, this setup ensures your blog stands out.
5. Designing with Tailwind CSS
With MDX configured and your content ready to shine, it’s time to focus on the visual aspect of your blog. Tailwind CSS provides a powerful utility-first framework that lets you design beautiful, responsive layouts with ease. In this section, we’ll walk through setting up Tailwind, customizing its configuration for a unique look, and implementing features like clean typography and dark mode.
Step 1: Configure tailwind.config.js
The tailwind.config.js
file is where you define your design system. Open the file and customize it to suit your blog’s aesthetic. Here’s an example configuration:
module.exports = {
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
colors: {
primary: '#1E3A8A', // A deep blue for headings and accents
secondary: '#FBBF24', // A warm yellow for highlights
dark: '#1F2937', // Dark background for dark mode
light: '#F9FAFB', // Light background for light mode
},
fontFamily: {
sans: ['Inter', 'sans-serif'], // Clean and modern font
serif: ['Merriweather', 'serif'], // Elegant serif for headings
},
spacing: {
'18': '4.5rem', // Custom spacing for layout adjustments
},
},
},
plugins: [],
darkMode: 'class', // Enable dark mode based on class
}
This configuration extends Tailwind’s default settings by adding custom colors, fonts, and spacing. The darkMode: 'class'
option allows you to toggle dark mode dynamically.
Step 2: Enhance Readability with Clean Typography
Typography plays a crucial role in making your blog easy to read and visually appealing. Tailwind’s utility classes make it simple to fine-tune your text styles. Here are some tips for creating clean typography:
-
Font Choices: Use a sans-serif font like
Inter
for body text to ensure readability. For headings, consider a serif font likeMerriweather
to add elegance. - Line Height and Spacing: Adjust line heights and letter spacing for optimal legibility. For example:
<h1 class="text-4xl font-serif leading-tight tracking-wide">Welcome to My Blog</h1>
<p class="text-lg font-sans leading-relaxed">This is a sample paragraph with clean typography.</p>
- Responsive Scaling: Use responsive utilities to adapt font sizes for different screen sizes:
<p class="text-base sm:text-lg md:text-xl lg:text-2xl">Scaling text for all devices.</p>
These small tweaks can significantly improve the reading experience, ensuring your content looks polished on any device.
Step 3: Implement Dark Mode
Dark mode has become a must-have feature for modern websites, and Tailwind makes it effortless to implement. With the darkMode: 'class'
setting in your tailwind.config.js
, you can toggle dark mode by adding or removing the dark
class on the <html>
or <body>
element.
Here’s how to style your blog for both light and dark modes:
<div class="bg-light dark:bg-dark text-dark dark:text-light">
<h1 class="text-3xl font-bold">Hello, World!</h1>
<p class="text-lg">This text adapts to light and dark modes seamlessly.</p>
</div>
To toggle dark mode dynamically, you can use JavaScript to add or remove the dark
class:
function toggleDarkMode() {
document.documentElement.classList.toggle('dark')
}
Add a button to your blog’s layout for users to switch modes:
<button onclick="toggleDarkMode()" class="px-4 py-2 bg-primary text-light rounded">
Toggle Dark Mode
</button>
Step 4: Ensure Accessible Contrast Ratios
When designing for dark mode, accessibility is key. Use tools like WebAIM Contrast Checker to ensure your color combinations meet WCAG standards. For example:
- Text on a dark background should have a contrast ratio of at least 4.5:1.
- Use Tailwind’s opacity utilities (
opacity-75
, etc.) to soften colors without compromising readability.
Why Tailwind CSS Makes Design Effortless
By leveraging Tailwind’s utility classes, you can rapidly prototype and refine your blog’s design without writing custom CSS. From clean typography to responsive layouts and dark mode support, Tailwind empowers you to create a visually stunning and user-friendly blog. Whether you’re tweaking spacing, experimenting with colors, or ensuring accessibility, Tailwind’s flexibility ensures your design remains consistent and professional.
With your design system in place, your blog is now ready to captivate readers with its aesthetics and functionality. Let’s move on to structuring your content!
6. Structuring Your Blog
A well-organized blog is the foundation of a great user experience. In this section, we’ll explore how to structure your blog posts effectively and leverage Next.js’s dynamic routing to fetch and display content seamlessly. By the end of this section, you’ll have a system in place to list all your posts on the homepage and render individual posts dynamically.
Step 1: Organizing Blog Posts
To keep your project tidy, it’s important to establish a clear folder structure for your .mdx
files. A common approach is to store all your blog posts in a dedicated directory, such as posts
. Here’s an example structure:
/pages
/posts
my-first-post.mdx
another-post.mdx
yet-another-post.mdx
Each .mdx
file represents a single blog post. You can also include metadata (like title, date, and tags) at the top of each file using frontmatter. For example:
---
title: "My First Blog Post"
date: "2023-10-01"
tags: ["nextjs", "mdx", "tailwindcss"]
---
# Welcome to My Blog
This is the content of my first post!
This metadata will later be used to display summaries on the homepage and organize posts by tags or categories.
Step 2: Fetching and Displaying Posts Dynamically
Next.js makes it easy to fetch and display your blog posts dynamically. Let’s start by creating a homepage that lists all your posts.
Listing All Posts on the Homepage
Create a file named index.js
inside the pages
directory. This will serve as your blog’s homepage. Use getStaticProps
to fetch all .mdx
files from the posts
directory and extract their metadata:
import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'
export default function Home({ posts }) {
return (
<div>
<h1 className="text-4xl font-bold mb-8">My Blog</h1>
<ul>
{posts.map((post, index) => (
<li key={index} className="mb-6">
<a href={`/posts/${post.slug}`} className="text-blue-500 hover:underline">
<h2 className="text-2xl font-semibold">{post.data.title}</h2>
</a>
<p className="text-gray-600">{post.data.date}</p>
</li>
))}
</ul>
</div>
)
}
export async function getStaticProps() {
const files = fs.readdirSync(path.join('pages/posts'))
const posts = files.map((filename) => {
const slug = filename.replace('.mdx', '')
const markdownWithMeta = fs.readFileSync(path.join('pages/posts', filename), 'utf-8')
const { data } = matter(markdownWithMeta)
return { slug, data }
})
return {
props: { posts },
}
}
This code reads all .mdx
files, extracts their metadata, and passes it to the homepage component. Each post is displayed as a clickable link that navigates to its respective page.
Rendering Individual Posts Dynamically
To render individual posts dynamically, create a file named [slug].js
inside the pages/posts
directory. This file uses dynamic routing to fetch and display the content of a specific post based on its slug:
import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'
import { serialize } from 'next-mdx-remote/serialize'
import { MDXRemote } from 'next-mdx-remote'
const PostPage = ({ source, frontmatter }) => {
return (
<div>
<h1 className="text-4xl font-bold mb-4">{frontmatter.title}</h1>
<p className="text-gray-600 mb-8">{frontmatter.date}</p>
<article className="prose lg:prose-xl dark:prose-invert">
<MDXRemote {...source} />
</article>
</div>
)
}
export async function getStaticPaths() {
const files = fs.readdirSync(path.join('pages/posts'))
const paths = files.map((filename) => ({
params: { slug: filename.replace('.mdx', '') },
}))
return {
paths,
fallback: false,
}
}
export async function getStaticProps({ params }) {
const markdownWithMeta = fs.readFileSync(
path.join('pages/posts', `${params.slug}.mdx`),
'utf-8'
)
const { data, content } = matter(markdownWithMeta)
const mdxSource = await serialize(content)
return {
props: { source: mdxSource, frontmatter: data },
}
}
export default PostPage
This setup uses getStaticPaths
to generate routes for each post and getStaticProps
to fetch the content and metadata for the requested post. The MDXRemote
component renders the MDX content dynamically, allowing you to embed React components seamlessly.
Why Dynamic Routing Matters
Dynamic routing ensures your blog scales effortlessly as you add more content. Instead of manually creating pages for each post, Next.js handles everything automatically based on your folder structure and metadata. This not only saves time but also keeps your project organized and maintainable.
By combining a clean folder structure with Next.js’s powerful data-fetching methods, you’ve now created a robust system for managing and displaying your blog posts. Readers can browse your homepage, click on a post, and enjoy a seamless reading experience—all while you focus on writing great content. Let’s move on to enhancing the user experience with additional features!
7. Enhancing User Experience
A great blog isn’t just about content and design—it’s also about the little details that make the user experience smooth, engaging, and accessible. In this section, we’ll explore additional features you can add to elevate your blog while keeping accessibility at the forefront of your build process.
Syntax Highlighting for Code Blocks
If your blog includes code snippets, syntax highlighting is a must-have feature to improve readability. Libraries like Prism or Shiki integrate seamlessly with MDX and Tailwind CSS. For example, you can use react-syntax-highlighter
to style your code blocks dynamically:
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
import { tomorrow } from 'react-syntax-highlighter/dist/cjs/styles/prism'
const CodeBlock = ({ children, language }) => {
return (
<SyntaxHighlighter language={language} style={tomorrow} className="rounded-md">
{children}
</SyntaxHighlighter>
)
}
export default CodeBlock
Embed this component in your MDX files to ensure all code snippets are visually appealing and easy to read. This small enhancement can make a big difference for developers reading your technical posts.
SEO Optimization with Metadata Tags
Search engine optimization (SEO) is crucial for driving traffic to your blog. Use Next.js’s built-in <Head>
component to add metadata tags like titles, descriptions, and Open Graph images to each page. For example:
import Head from 'next/head'
const PostPage = ({ frontmatter }) => {
return (
<>
<Head>
<title>{frontmatter.title} | My Blog</title>
<meta name="description" content={frontmatter.excerpt || 'A blog post about web development'} />
<meta property="og:title" content={frontmatter.title} />
<meta property="og:description" content={frontmatter.excerpt || 'A blog post about web development'} />
<meta property="og:image" content="/images/og-image.png" />
</Head>
{/* Rest of your post content */}
</>
)
}
These tags help search engines understand your content better and improve your blog’s visibility in search results.
Social Sharing Buttons and Analytics Integration
Encourage readers to share your posts by adding social sharing buttons. You can use libraries like react-share
to create customizable buttons for platforms like Twitter, LinkedIn, and Facebook. For example:
import { TwitterShareButton, LinkedinShareButton } from 'react-share'
const ShareButtons = ({ url, title }) => {
return (
<div className="flex space-x-4 mt-8">
<TwitterShareButton url={url} title={title}>
Share on Twitter
</TwitterShareButton>
<LinkedinShareButton url={url} summary={title}>
Share on LinkedIn
</LinkedinShareButton>
</div>
)
}
Additionally, integrate analytics tools like Google Analytics or Plausible to track visitor behavior and gain insights into your audience. Use Next.js’s _app.js
file to initialize the analytics script globally.
Accessibility Best Practices
Throughout the build process, prioritize accessibility to ensure your blog is inclusive and usable for everyone. Use semantic HTML elements like <header>
, <main>
, and <footer>
to structure your pages logically. Ensure sufficient color contrast for text and interactive elements, and test your site with screen readers to identify potential issues. Tailwind CSS’s utility classes like sr-only
and focus-visible
can help you implement accessibility features effectively.
By incorporating these enhancements—syntax highlighting, SEO optimization, social sharing buttons, analytics, and accessibility best practices—you’re not just building a blog; you’re creating an experience that delights readers and keeps them coming back for more. Let’s wrap things up with deployment!
8. Deploying Your Blog
Deploying your blog is the final step in bringing your hard work to life, and with modern tools, it’s easier than ever. Follow this step-by-step guide to get your blog live and take advantage of continuous deployment and automatic builds.
Step 1: Build the Project Locally
Before deploying, ensure your project is ready for production by building it locally. Run the following command in your terminal:
npm run build
This generates an optimized version of your blog in the .next
folder. Test the build by running:
npm start
Visit http://localhost:3000
to confirm everything works as expected. If all looks good, you’re ready to move on.
Step 2: Push the Code to a Git Repository
Next, push your code to a Git repository like GitHub, GitLab, or Bitbucket. If you haven’t initialized a repository yet, do so by running:
git init
git add .
git commit -m "Initial commit"
Then, create a new repository on your chosen platform and link it to your local project:
git remote add origin https://github.com/your-username/your-repo-name.git
git branch -M main
git push -u origin main
This ensures your code is safely stored and ready for deployment.
Step 3: Connect the Repository to the Deployment Platform
Most modern deployment platforms integrate seamlessly with Git repositories, enabling automatic builds and deployments whenever you push updates. Navigate to your chosen platform (e.g., Vercel, Netlify, or Cloudflare Pages) and follow these steps:
- Sign in to your account and select “New Project” or “Add Site.”
- Connect your Git repository by authorizing access to your account.
- Choose the repository containing your blog and configure the deployment settings (most platforms detect Next.js projects automatically).
- Trigger the first deployment by clicking “Deploy” or pushing a new commit to the main branch.
Once the deployment process completes, your blog will be live, and you’ll receive a URL to share with the world.
Continuous Deployment and Automatic Builds
One of the biggest advantages of this workflow is the ease of continuous deployment. With your repository connected, every time you push changes to the main branch, the platform automatically rebuilds and deploys your blog. This means you can focus on writing and improving your content without worrying about manual updates. Additionally, many platforms offer features like preview environments for pull requests, allowing you to test changes before merging them into production.
By leveraging these tools, you ensure your blog is always up-to-date, performant, and ready to reach your audience. With your blog now live, it’s time to celebrate your achievement and start sharing your ideas with the world!
9. Wrap-up!
Building a blog with Next.js, MDX, and Tailwind CSS is a game-changer for developers and content creators alike. This powerful stack offers unmatched flexibility, performance, and customization options, enabling you to craft a blog that’s both functional and visually stunning. With Next.js handling routing and optimization, MDX blending dynamic content with Markdown simplicity, and Tailwind CSS providing a utility-first approach to design, you have all the tools you need to create a blog that stands out.
Whether you’re writing technical tutorials, sharing personal stories, or showcasing your projects, this stack empowers you to focus on what matters most—your content. Now is the perfect time to experiment with customizations, explore new features, and make your blog truly unique. Share your creations with the community and inspire others to embark on their own blogging journey. Ready to take the plunge? Start building your own blog today!
10. Bonus Tips (Optional Section)
Maintaining and scaling your blog is just as important as building it, and with a few strategic tips, you can ensure it remains robust and adaptable as your audience grows. Here are some quick ideas to take your blog to the next level:
Automating Backups
Protect your hard work by setting up automated backups for your content and database (if applicable). Use tools like GitHub Actions or third-party services to schedule regular snapshots of your repository. For added security, consider exporting your .mdx
files periodically and storing them in cloud storage solutions like AWS S3 or Google Drive.
Exploring Advanced Tailwind Configurations
Tailwind CSS is incredibly versatile, and diving deeper into its configuration can unlock even more potential for your blog. Experiment with features like custom plugins, responsive design utilities, and advanced color palettes to refine your design system. For example, you can create reusable components using Tailwind’s @apply
directive or extend your theme to include unique typography scales that match your brand identity.
Integrating CMS Options for Non-Technical Contributors
If you plan to collaborate with non-technical contributors, integrating a headless CMS like Contentful or Sanity can streamline the content creation process. These platforms allow users to write and manage posts through an intuitive interface while seamlessly connecting to your Next.js app via APIs. By combining the flexibility of MDX with the ease of a CMS, you can empower your team to focus on storytelling without worrying about code.
By implementing these bonus tips, you’ll not only future-proof your blog but also make it easier to manage and scale over time. Whether you’re automating workflows, refining your design, or enabling collaboration, these strategies will help you maintain a polished and professional presence online.
This content originally appeared on DEV Community and was authored by gerry leo nugroho

gerry leo nugroho | Sciencx (2025-03-02T00:10:59+00:00) Building a Modern Blog with Next.js, MDX, and Tailwind CSS. Retrieved from https://www.scien.cx/2025/03/02/building-a-modern-blog-with-next-js-mdx-and-tailwind-css/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.