Next.js Image Optimization for MDX - Dev Log
Implementation guide for adding Next.js Image optimization to MDX content using next-mdx-remote
Next.js Image Optimization for MDX - Dev Log
📝 AI Documentation Note: This dev log was generated by AI to document the technical implementation process. It serves as a reference guide for the image optimization solution implemented in this project.
Problem Statement
MDX content renders markdown images as basic HTML <img>
tags without optimization. This results in:
- No lazy loading
- No format conversion (WebP/AVIF)
- No responsive sizing
- Poor performance metrics
Solution Overview
Implement a custom MDX component that wraps Next.js Image
component to automatically optimize all markdown images while preserving simple markdown syntax.
Implementation
Requirements
- Next.js with built-in
Image
component next-mdx-remote
for MDX rendering- Tailwind CSS for styling
Step 1: MDX Image Component
Create src/components/MDXImage.tsx
:
import Image from 'next/image';
import React from 'react';
interface MDXImageProps extends React.HTMLAttributes<HTMLElement> {
src: string;
alt: string;
width?: number;
height?: number;
}
export function MDXImage({ src, alt, width, height }: MDXImageProps) {
if (!src) return null;
return (
<span className="block my-8 flex justify-center">
<Image
src={src}
alt={alt || ''}
width={width || 800}
height={height || 450}
className="rounded-lg shadow-lg max-w-full h-auto"
priority={false}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABIADdAAQ3AACEAAgEBAEBAwEBAA//2wBDABQODxIPDRQSEBIXFRQYHjIhHhwcHj0sLiQySUBMS0dARkVQWnNiUFVtVkVGZIhlbXd7gYKBTmCNl4x9lnN+gXz/2wBDARUXFx4aHjshITt8U0ZTfHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHz/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABmX/9k="
/>
</span>
);
}
Step 2: Component Integration
Wire the component with next-mdx-remote
:
// src/app/posts/[slug]/page.tsx
import { MDXRemote } from 'next-mdx-remote/rsc'
import { MDXImage } from '@/components/MDXImage'
const components = {
img: MDXImage,
}
// In render
<MDXRemote source={content} components={components} />
Critical Fix: Hydration Error
Issue: MDX wraps images in <p>
tags, but <div>
elements cannot be nested inside <p>
tags.
Error:
In HTML, <div> cannot be a descendant of <p>.
This will cause a hydration error.
Solution: Use <span>
with block
class instead of <div>
:
// ❌ Causes hydration error
<div className="my-8 flex justify-center">
// ✅ Correct implementation
<span className="block my-8 flex justify-center">
Results
Performance Improvements
- Before: 2.8MB PNG loads immediately, full size
- After: Converted to WebP (20-50% smaller), lazy loaded, blur placeholder
Technical Benefits
- Images served via
/_next/image?url=...
endpoint - Automatic format conversion (WebP/AVIF)
- Lazy loading with intersection observer
- Responsive sizing based on viewport
- Blur placeholder during load
CDN Integration
When deployed on Vercel:
- Automatic edge caching
- Global CDN distribution
- On-demand image optimization
Alternative Approaches Considered
- @next/mdx: Requires different setup, less flexible for our use case
- Remark plugins: Added complexity for simple image transformation
- Direct JSX in MDX: Lost simple markdown syntax
- Third-party services: Unnecessary for basic optimization needs
Dependencies
- Next.js (built-in Image component)
- next-mdx-remote (component mapping)
- Tailwind CSS (styling)
Configuration
No additional packages required. Uses existing Next.js image optimization pipeline.
Notes
- Works with any CDN via Next.js image loader configuration
- Maintains markdown syntax for content writing
- Handles missing src gracefully
- Default dimensions: 800x450px