@danielsanjp/next-mdx-blog
v1.0.4
Published
A simple, beautiful MDX-based blog system for Next.js with TypeScript support. Zero config, SEO optimized, Table of Contents, and highly customizable.
Downloads
22
Maintainers
Readme
Next MDX Blog
A simple, beautiful blog system for Next.js 15+ with MDX support. Write your blog posts in MDX and get a professional blog with zero configuration.
Features
- MDX Support - Write blog posts in MDX with full JSX support
- TypeScript - Fully typed for better development experience
- Zero Config - Works out of the box
- SEO Optimized - Automatic metadata and Open Graph tags
- Table of Contents - Auto-generated sticky sidebar with active section highlighting
- Reading Time - Automatic reading time calculation
- Tags & Categories - Organize posts with tags
- Beautiful Styling - Professional, responsive design
- Static Generation - All pages statically generated for best performance
- Easy Setup - CLI tool to scaffold everything in seconds
Installation
npm install @danielsanjp/next-mdx-blogDependencies
This package requires a Next.js 15+ project with Tailwind CSS configured. Additional dependencies will be installed automatically:
npm install gray-matter reading-time next-mdx-remote remark-gfm rehype-slugQuick Start
1. Run Setup
npx @danielsanjp/next-mdx-blogThis will:
- Create blog pages in
src/app/blog/(orapp/blog/if no src directory) - Create a
content/blog/directory with an example post - Create/update
mdx-components.tsxfor MDX rendering
2. Write Your First Post
Create a file in content/blog/my-first-post.mdx:
---
title: "My First Blog Post"
description: "This is my first blog post using Next MDX Blog"
date: "2025-10-15"
author: "Your Name"
tags: ["Getting-started", "Tutorial"]
# Optional: Add a cover image
# image: "/blog/my-first-post.jpg"
# imageAlt: "Blog post cover image"
---
# Hello World!
This is my first blog post. I can use **markdown** and even _JSX components_!
## Features I Love
- Easy to write
- Fast performance
- Great SEO
Check out more at [my website](https://example.com).3. Visit Your Blog
Start your Next.js dev server and visit:
/blog- See all your posts/blog/my-first-post- View individual post
What Gets Created
The setup command creates these files in your project:
Blog Pages
app/blog/page.tsx- Blog index with all postsapp/blog/[slug]/page.tsx- Individual blog post pages
Content & Utilities
content/blog/example-post.mdx- Example blog postlib/blog.tsx- Blog data fetching functions (fully customizable)lib/mdx-components.tsx- MDX styling components (shadcn/ui standards)mdx-components.tsx- Root MDX config (required by Next.js)
Functions Available
After setup, you can use these functions from @/lib/blog:
// Get all blog posts (sorted by date)
const posts = await getAllPosts();
// Get a specific post
const post = await getPostBySlug("my-first-post");
// Get posts by tag
const taggedPosts = await getPostsByTag("Tutorial");
// Get all unique tags
const tags = await getAllTags();Blog Post Interface
interface BlogPost {
slug: string;
title: string;
date: string;
excerpt: string;
author?: string;
tags?: string[];
readingTime?: number;
content?: React.ReactElement;
image?: string;
imageAlt?: string;
}Frontmatter Fields
| Field | Type | Required | Description |
| ------------- | -------- | -------- | ----------------------------- |
| title | string | Yes | Post title |
| description | string | Yes | Post excerpt/description |
| date | string | Yes | Publication date (ISO format) |
| author | string | No | Author name |
| tags | string[] | No | Array of tags |
| image | string | No | Cover image URL |
| imageAlt | string | No | Image alt text |
Customization
All files are copied to your project, giving you full control to customize everything:
Custom MDX Components
Edit lib/mdx-components.tsx to style your blog posts:
// lib/mdx-components.tsx
const mdxComponents: MDXComponents = {
h1: ({ children }) => (
<h1 className="text-4xl font-bold text-primary">{children}</h1>
),
// ... customize all elements
};The default styling follows shadcn/ui typography standards for a clean, professional look.
Blog Functions
Edit lib/blog.tsx to customize data fetching:
// Change content directory
const contentDirectory = path.join(process.cwd(), "posts"); // instead of "content/blog"
// Add custom post processing
// Modify sorting, filtering, etc.Styling
The generated pages use Tailwind CSS. Customize these classes in the template files:
container- Main containerbg-background,text-foreground- Theme colorsbg-muted,text-muted-foreground- Muted colorstext-primary,bg-primary- Primary colorsborder- Border colors
You can customize these in your tailwind.config.js.
Page Templates
After running setup, you can freely edit the generated pages:
app/blog/page.tsx- Blog index pageapp/blog/[slug]/page.tsx- Individual post page
File Structure
your-nextjs-project/
├── content/
│ └── blog/
│ ├── my-first-post.mdx
│ └── another-post.mdx
├── src/
│ └── app/
│ └── blog/
│ ├── page.tsx # Blog index
│ └── [slug]/
│ └── page.tsx # Individual post
├── mdx-components.tsx
└── package.jsonAPI Reference
After setup, you'll have these functions in lib/blog.tsx:
getAllPosts(): Promise<BlogPost[]>
Returns all blog posts sorted by date (newest first).
import { getAllPosts } from "@/lib/blog";
const posts = await getAllPosts();getPostBySlug(slug: string): Promise<BlogPost | null>
Get a single post by its slug (filename without extension).
import { getPostBySlug } from "@/lib/blog";
const post = await getPostBySlug("my-post");getPostsByTag(tag: string): Promise<BlogPost[]>
Get all posts with a specific tag.
import { getPostsByTag } from "@/lib/blog";
const posts = await getPostsByTag("Tutorial");getAllTags(): Promise<string[]>
Get all unique tags used across all posts.
import { getAllTags } from "@/lib/blog";
const tags = await getAllTags();Requirements
- Next.js 15.0.0 or higher
- React 18.0.0 or higher
- Tailwind CSS
- TypeScript (recommended)
Tips
Table of Contents
The blog automatically generates a sticky sidebar Table of Contents for each post:
- Auto-extracts all
h2andh3headings - Smooth scroll to sections
- Active section highlighting as you scroll
- Hidden on mobile, visible on large screens (xl breakpoint)
- Styled with shadcn/ui design standards
How it works:
rehype-slugautomatically adds IDs to all headings- The
TableOfContentscomponent extracts headings from the DOM - Uses Intersection Observer to track which section is visible
- Smooth scrolls when clicking TOC links
Customize the TOC:
Edit components/TableOfContents.tsx to:
- Change which heading levels are included (currently h2 and h3)
- Modify the styling and colors
- Adjust the scroll offset
- Change the Intersection Observer thresholds
Images in Blog Posts
Adding cover images:
- Place images in
public/blog/directory - Reference them in frontmatter:
---
image: "/blog/my-image.jpg"
imageAlt: "Description for accessibility"
---No image? No problem! A beautiful placeholder will be shown automatically with a hint to add an image.
Syntax Highlighting
Install a code highlighting library like rehype-highlight or rehype-prism-plus and configure in your MDX compilation.
Draft Posts
Add a draft: true field to frontmatter and filter them out:
const posts = await getAllPosts();
const published = posts.filter((p) => !p.draft);Examples
Custom Blog Index
import { getAllPosts, getAllTags } from "@/lib/blog";
export default async function BlogPage() {
const posts = await getAllPosts();
const tags = await getAllTags();
return (
<div>
<h1>Blog</h1>
<div className="tags">
{tags.map((tag) => (
<span key={tag}>{tag}</span>
))}
</div>
<div className="posts">
{posts.map((post) => (
<article key={post.slug}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
</div>
);
}RSS Feed
Create app/blog/rss.xml/route.ts:
import { getAllPosts } from "@/lib/blog";
export async function GET() {
const posts = await getAllPosts();
const rss = `<?xml version="1.0"?>
<rss version="2.0">
<channel>
<title>My Blog</title>
${posts
.map(
(post) => `
<item>
<title>${post.title}</title>
<link>https://example.com/blog/${post.slug}</link>
<description>${post.excerpt}</description>
<pubDate>${new Date(post.date).toUTCString()}</pubDate>
</item>
`
)
.join("")}
</channel>
</rss>`;
return new Response(rss, {
headers: { "Content-Type": "application/xml" },
});
}Troubleshooting
MDX not rendering
Run the setup command: npx @danielsanjp/next-mdx-blog to ensure all required files are created.
Styles not working
Make sure Tailwind CSS is properly configured in your Next.js project.
License
MIT
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Support
If you have questions or need help, please open an issue on GitHub.
Made with ❤️ for the Next.js community
