Last modified: July 14, 2025
·
7 min read

How to add syntax highlighting to MDX files in Next.js

Syntax highlighting is essential for any technical blog or documentation site. It makes code blocks more readable and visually appealing, helping readers understand the structure and syntax of your code examples. In this guide, we'll walk through how to integrate highlight.js into your Next.js MDX setup using rehype-highlight, with proper dark/light theme support and Tailwind CSS compatibility.

Why highlight.js with rehype-highlight?

When working with MDX in Next.js, you need a solution that processes your markdown at build time and applies syntax highlighting. The rehype-highlight plugin is the perfect choice because:

  • Build-time processing: Syntax highlighting is applied during the build process, not at runtime
  • Lightweight: Only the necessary highlight.js styles are included
  • Flexible: Supports custom themes and modifications
  • MDX compatible: Works seamlessly with Next.js MDX setup

Step 1: Install Dependencies

First, install the required packages:

npm install rehype-highlight

The rehype-highlight package will automatically install highlight.js as a dependency.

Step 2: Configure Next.js MDX

Update your next.config.ts to include the rehype-highlight plugin:

import type { NextConfig } from "next";
import createMDX from '@next/mdx'
import rehypeHighlight from 'rehype-highlight';

const nextConfig: NextConfig = {
  output: 'export',
  trailingSlash: true,
  pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
  images: {
    unoptimized: true,
  }
};

const withMDX = createMDX({
  extension: /\.(md|mdx)$/,
  options: {
    remarkPlugins: [],
    rehypePlugins: [rehypeHighlight],
  }
})
 
export default withMDX(nextConfig)

This configuration tells Next.js to process all .md and .mdx files with the rehype-highlight plugin, which will automatically detect code blocks and apply syntax highlighting.

Step 3: Create Custom CSS Styles

You'll need to copy the highlight.js CSS themes from the official repository and make some modifications for optimal integration with your Next.js setup.

Create a custom CSS file and copy your preferred theme (like github.css for light mode and github-dark.css for dark mode). Here are the key modifications you'll need to make:

Important modifications:

  1. Remove background colors: If you're using Tailwind Typography, remove the background-color from the .hljs class to avoid conflicts with the prose styling
  2. Add dark mode support: Wrap the dark theme styles in a .dark class selector to enable automatic theme switching
  3. Preserve syntax colors: Keep all the syntax highlighting colors for both light and dark themes

Example of your highlight-js.css file:

/* Copy light theme styles from highlight.js here*/
.hljs {
  color: #24292e;
  /* Remove background-color if using Tailwind Typography */
}

/* ... other light theme styles ... */

/* Dark theme styles wrapped in .dark class */
.dark {
  /* Copy dark theme styles from highlight.js here*/
  .hljs {
    color: #c9d1d9;
  }
  /* ... other dark theme styles ... */
}

Step 4: Import CSS in MDX Layout

Create or update your MDX layout component to import the highlight.js styles:

import React from "react";
import "@/app/styles/highlight-js.css"

export default function MdxLayout({ children }: { children: React.ReactNode }) {
    return (
      <div>
        {children}
      </div>
    )
  }

This ensures that the highlight.js styles are loaded for all MDX content.

Step 5: Using Code Blocks in MDX

Now you can use code blocks in your MDX files with automatic syntax highlighting:

# Example MDX Content

Here's a JavaScript example:

```javascript
function greet(name) {
  return `Hello, ${name}!`;
}

console.log(greet('World'));
```

And a TypeScript example:

```typescript
interface User {
  id: number;
  name: string;
  email: string;
}

const user: User = {
  id: 1,
  name: 'John Doe',
  email: 'john@example.com'
};
```

Python code works too:

```python
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(10))
```

The rehype-highlight plugin will automatically detect the language and apply the appropriate syntax highlighting.

Step 6: Language Detection

rehype-highlight automatically detects the language from the code fence. Supported languages include:

  • JavaScript/TypeScript: js, javascript, ts, typescript
  • Python: python, py
  • Java: java
  • CSS: css
  • HTML: html
  • JSON: json
  • Bash/Shell: bash, sh, shell
  • SQL: sql
  • Markdown: markdown, md
  • And many more...

Customization Options

Using Different Themes

You can easily switch to different highlight.js themes by replacing the CSS content. Popular alternatives include:

  • Monokai: Dark theme with bright colors
  • Dracula: Dark theme with purple accents
  • Solarized Light/Dark: Easy on the eyes
  • VS Code: Matches VS Code's default theme

Custom Language Support

By default, rehype-highlight supports only (37 languages)[https://github.com/wooorm/lowlight?tab=readme-ov-file#common] defined in common object of lowlight library. If you want to support all languages or a customized subset of languages, you can configure rehype-highlight:

import rehypeHighlight from 'rehype-highlight';
import elixir from 'highlight.js/lib/languages/elixir';
import rust from 'highlight.js/lib/languages/rust';
import { all } from 'lowlight';

const withMDX = createMDX({
  extension: /\.(md|mdx)$/,
  options: {
    remarkPlugins: [],
    rehypePlugins: [
      [rehypeHighlight,  { languages: all }] // supports all languages
      //[rehypeHighlight, { languages: {elixir, rust} }] // supports only elixir and rust languages
    ],
  }
})

Troubleshooting

Code Blocks Not Highlighting

  1. Check language specification: Make sure you specify the language in the code fence
  2. Verify plugin installation: Ensure rehype-highlight is properly installed
  3. Check CSS import: Confirm the highlight.js CSS is imported in your MDX layout

Styling Conflicts

  1. Tailwind Typography conflicts: The custom CSS removes background colors to prevent conflicts
  2. Dark mode issues: Ensure your dark mode implementation uses the .dark class
  3. Font conflicts: The CSS preserves font styling while adding syntax colors

Performance Considerations

  • Build time: Syntax highlighting happens at build time, so it doesn't affect runtime performance
  • Bundle size: Only the necessary highlight.js styles are included
  • Caching: Highlighted code blocks are cached with your static export

Best Practices

  1. Always specify language: Even if auto-detection works, explicitly specifying the language ensures consistent highlighting
  2. Use semantic language names: Use typescript instead of ts for better clarity
  3. Test both themes: Ensure your code looks good in both light and dark modes
  4. Keep CSS minimal: Only include the styles you need to avoid conflicts

Conclusion

Integrating highlight.js with MDX in Next.js provides a powerful, performant solution for syntax highlighting. The combination of rehype-highlight for processing and custom CSS for styling gives you full control over the appearance while maintaining compatibility with Tailwind CSS and dark mode support.

The setup we've covered provides:

  • Automatic syntax highlighting for code blocks
  • Dark/light theme support
  • Tailwind CSS compatibility
  • Build-time processing for optimal performance
  • Support for 190+ programming languages
  • Customizable themes and styling

This approach is a good fit for technical blogs, documentation sites, and any project that needs to display code with proper syntax highlighting.