How to use Next.js Image with Markdown (or MDX!)

Perhaps you've heard of the amazing Next.js Image component but found yourself lost when trying to implement it on your markdown blog. If so, it's hardly your fault. After all, Vercel and the Next.js docs don't give much away on how to use it. So here's a simple guide on how to use Next.js Image with Markdown or MDX.

A red and azure illustration with the caption: "Use Next.js Image with Markdown"

The Image component is pretty fantastic:

  • It converts images to webp, the most performant image format on the web (which is pretty difficult to do on your own, believe me)
  • It automatically handles lazy loading for a performance boost
  • It serves all images from Vercel's global CDN

But if you've tried to use it in a blog or a page created from markdown, it's likely you've stumbled across the "image width and height are required" error.

This is usually not a problem as they can be manually set. But when your images are simply embedded in a markdown file, there's not even a way to manually set these values, let alone having to do it for dozens of them across multiple articles!

Using Next.js Image with Markdown

Thankfully, the solution is quite simple, and the basics should be usable even if you're using a different markdown to html solution. Here I'll specifically explain how to use Next.js Image with markdown using next-mdx-remote, but if you're having trouble with another package leave a comment and I'll try to adapt it!

The trick here is to use rehype plugins to add height and width information of our images to the markdown as we load it. Specifically, I've been using rehype-image-size. It's simple and easy to use.

Give it an install and let's look at how to implement it:

npm i rehype-image-size

or

yarn add rehype-image-size

Next.js Image with next-mdx-remote

next-mdx-remote is a great and easy to use package which contains utilities to parse as well as display your markdown rolled into one. Contrary to what the name might suggest, it works with standard markdown as well, so I highly suggest giving it a try.

First of all we have to serialise our markdown, we'll do this in getStaticProps.

import { serialize } from "next-mdx-remote/serialize";
import imageSize from "rehype-img-size";

export const getStaticProps: GetStaticProps = async ({ params }) => {
  // get your markdown here

  // pass your markdown string to the serialize function
  const mdxSource = await serialize(markdown, {
    mdxOptions: {
      // use the image size plugin, you can also specify which folder to load images from
      // in my case images are in /public/images/, so I just prepend 'public'
      rehypePlugins: [[imageSize, { dir: "public" }]],
    },
  });

  // return your serialized content as a prop here
  return {
    props: {
      post: {
        mdxSource,
      },
    },
  };
};

At this point your mdxSource prop will already have both height and width, so all you need to do is render it correctly:

import Image from "next/image";
import { MDXRemote } from "next-mdx-remote";

// this object will contain all the replacements we want to make
const components = {
  img: (props) => (
    // height and width are part of the props, so they get automatically passed here with {...props}
    <Image {...props} layout="responsive" loading="lazy" />
  ),
};

function Post({ post }: { post: any }) {
  return (
    <>
      {/* MDXRemote uses the components prop to decide which html elements to switch for components */}
      <MDXRemote {...post.mdxSource} components={components} />
    </>
  );
}

Run your development server and ta-daaa! You'll be using the Next.js Image with Markdown!

Again, if you have any questions just let me know.

New to Next.js and Markdown? Read up on how to read markdown from Next.js API routes.