Next.js Image Optimization for MDX - Dev Log

DEV LOGAI WrittenNext.jsMDXPerformanceImages

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.

Image optimization

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

  1. @next/mdx: Requires different setup, less flexible for our use case
  2. Remark plugins: Added complexity for simple image transformation
  3. Direct JSX in MDX: Lost simple markdown syntax
  4. 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

Continue Reading

Browse All Articles