Advanced MDX Blog Features You Should Implement Today

Discover powerful MDX blog features including custom components, table of contents generation, advanced syntax highlighting, and image optimization techniques.

Taking Your MDX Blog to the Next Level

Building a basic MDX blog is fairly straightforward, but implementing advanced features can significantly enhance the reading experience and set your blog apart. Let's explore some powerful features you should consider adding to your MDX-powered blog.

Automatic Table of Contents

A table of contents makes longer posts more navigable. Here's how to implement an automatic TOC component that extracts headings from your MDX content:

import { useEffect, useState } from 'react'
 
export function TableOfContents({ headings }) {
  const [activeId, setActiveId] = useState('')
 
  useEffect(() => {
    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            setActiveId(entry.target.id)
          }
        })
      },
      { rootMargin: '0px 0px -80% 0px' }
    )
 
    const elements = headings.map((h) => document.getElementById(h.id))
    elements.forEach((el) => el && observer.observe(el))
 
    return () => elements.forEach((el) => el && observer.unobserve(el))
  }, [headings])
 
  return (
    <nav className="toc">
      <h2 className="text-lg font-semibold mb-3">Table of Contents</h2>
      <ul className="space-y-2 text-sm">
        {headings.map((heading) => (
          <li
            key={heading.id}
            className={`${
              activeId === heading.id
                ? 'text-blue-600 font-medium'
                : 'text-gray-600'
            } hover:text-blue-800 transition-colors ml-${(heading.level - 2) * 4}`}
          >
            <a href={`#${heading.id}`}>{heading.text}</a>
          </li>
        ))}
      </ul>
    </nav>
  )
}

This component not only displays the TOC but also highlights the currently visible section as the user scrolls through the article.

Advanced Code Syntax Highlighting

Enhance code blocks with line highlighting, line numbers, and copy buttons:

import { useEffect, useState } from 'react'
import Prism from 'prismjs'
 
export function CodeBlock({ children, className, metastring }) {
  const [copied, setCopied] = useState(false)
  const language = className?.replace(/language-/, '') || 'text'
 
  // Parse metastring for line highlighting
  const highlightLines =
    metastring
      ?.match(/{([\d,-]+)}/)?.[1]
      .split(',')
      .flatMap((range) => {
        if (range.includes('-')) {
          const [start, end] = range.split('-').map(Number)
          return Array.from({ length: end - start + 1 }, (_, i) => start + i)
        }
        return [Number(range)]
      }) || []
 
  useEffect(() => {
    Prism.highlightAll()
  }, [children])
 
  const handleCopy = () => {
    navigator.clipboard.writeText(children)
    setCopied(true)
    setTimeout(() => setCopied(false), 2000)
  }
 
  return (
    <div className="relative group">
      <button
        onClick={handleCopy}
        className="absolute right-2 top-2 text-sm px-2 py-1 bg-gray-700 text-white rounded opacity-0 group-hover:opacity-100 transition-opacity"
      >
        {copied ? 'Copied!' : 'Copy'}
      </button>
      <pre className={`${className} relative`}>
        {children.split('\n').map((line, i) => (
          <div
            key={i}
            className={`${
              highlightLines.includes(i + 1)
                ? 'bg-yellow-100 dark:bg-yellow-900/30'
                : ''
            } px-4`}
          >
            <span className="inline-block w-8 text-gray-400 text-right mr-4">
              {i + 1}
            </span>
            <code>{line}</code>
          </div>
        ))}
      </pre>
    </div>
  )
}

Image Optimization and Galleries

Optimize images and create beautiful galleries:

import { useState } from 'react'
import Image from 'next/image'
 
export function OptimizedImage({ src, alt, width, height, sizes }) {
  return (
    <div className="my-8">
      <Image
        src={src}
        alt={alt}
        width={width}
        height={height}
        sizes={sizes || '(max-width: 768px) 100vw, 768px'}
        className="rounded-lg"
        loading="lazy"
      />
      {alt && <p className="text-sm text-center text-gray-500 mt-2">{alt}</p>}
    </div>
  )
}
 
export function ImageCompare({ before, after }) {
  const [position, setPosition] = useState(50)
 
  return (
    <div
      className="relative h-[400px] my-8 overflow-hidden rounded-lg border"
      onMouseMove={(e) => {
        const rect = e.currentTarget.getBoundingClientRect()
        const x = e.clientX - rect.left
        const pct = (x / rect.width) * 100
        setPosition(pct)
      }}
    >
      <div className="absolute inset-0">
        <Image src={after} alt="After" fill className="object-cover" />
      </div>
      <div className="absolute inset-0" style={{ width: `${position}%` }}>
        <Image src={before} alt="Before" fill className="object-cover" />
      </div>
      <div
        className="absolute top-0 bottom-0 w-1 bg-white cursor-ew-resize"
        style={{ left: `${position}%` }}
      />
    </div>
  )
}

Creating Interactive Diagrams

Embed interactive diagrams in your technical posts:

import { useEffect, useRef } from 'react'
import mermaid from 'mermaid'
 
export function Mermaid({ chart }) {
  const ref = useRef(null)
 
  useEffect(() => {
    if (ref.current) {
      mermaid.initialize({ startOnLoad: true, theme: 'neutral' })
      mermaid.run()
    }
  }, [])
 
  return (
    <div className="my-8 p-4 bg-gray-50 dark:bg-gray-800 rounded-lg overflow-auto">
      <div className="mermaid" ref={ref}>
        {chart}
      </div>
    </div>
  )
}

Usage in MDX:

<Mermaid
  chart={`
graph TD
    A[Hard edge] -->|Link text| B(Round edge)
    B --> C{Decision}
    C -->|One| D[Result one]
    C -->|Two| E[Result two]
`}
/>

Custom Callout Components

Create eye-catching callouts for important information:

export function Callout({ children, type = 'info' }) {
  const styles = {
    info: 'bg-blue-50 border-blue-500 text-blue-800 dark:bg-blue-900/30 dark:border-blue-700 dark:text-blue-300',
    warning:
      'bg-yellow-50 border-yellow-500 text-yellow-800 dark:bg-yellow-900/30 dark:border-yellow-700 dark:text-yellow-300',
    error:
      'bg-red-50 border-red-500 text-red-800 dark:bg-red-900/30 dark:border-red-700 dark:text-red-300',
    success:
      'bg-green-50 border-green-500 text-green-800 dark:bg-green-900/30 dark:border-green-700 dark:text-green-300',
  }
 
  const icons = {
    info: '📌',
    warning: '⚠️',
    error: '❌',
    success: '✅',
  }
 
  return (
    <div className={`my-6 p-4 border-l-4 rounded-r-lg ${styles[type]}`}>
      <div className="flex">
        <div className="flex-shrink-0 mr-2">{icons[type]}</div>
        <div>{children}</div>
      </div>
    </div>
  )
}

Usage in MDX:

<Callout type="warning">
  Be careful when implementing this feature as it may have unintended side
  effects.
</Callout>

Progressive Loading and Reading Progress

Enhance the reading experience with a progress indicator:

export function ReadingProgress() {
  const [completion, setCompletion] = useState(0)
 
  useEffect(() => {
    const updateProgress = () => {
      const currentPosition = window.scrollY
      const documentHeight = document.body.scrollHeight - window.innerHeight
      const scrollPercent = (currentPosition / documentHeight) * 100
      setCompletion(scrollPercent)
    }
 
    window.addEventListener('scroll', updateProgress)
    return () => window.removeEventListener('scroll', updateProgress)
  }, [])
 
  return (
    <div className="fixed top-0 left-0 w-full h-1 bg-gray-200 z-50">
      <div
        className="h-full bg-blue-600 transition-all duration-150 ease-out"
        style={{ width: `${completion}%` }}
      />
    </div>
  )
}

Implementing these Features

To add these components to your MDX blog, you'll need to make them available through your MDX provider:

import { MDXProvider } from '@mdx-js/react'
import {
  Callout,
  CodeBlock,
  ImageCompare,
  Mermaid,
  OptimizedImage,
  ReadingProgress,
  TableOfContents,
} from './components'
 
const components = {
  code: CodeBlock,
  img: OptimizedImage,
  TableOfContents,
  ImageCompare,
  Mermaid,
  Callout,
  ReadingProgress,
}
 
export function MDXLayout({ children, frontmatter }) {
  const headings = extractHeadings(children)
 
  return (
    <MDXProvider components={components}>
      <ReadingProgress />
      <div className="grid grid-cols-1 lg:grid-cols-4 gap-10">
        <article className="lg:col-span-3">
          <h1>{frontmatter.title}</h1>
          {children}
        </article>
        <aside className="lg:col-span-1 sticky top-20 self-start">
          <TableOfContents headings={headings} />
        </aside>
      </div>
    </MDXProvider>
  )
}

By implementing these advanced features, you'll create a blog that's not only informative but also engaging and easy to navigate.