Colocated images with MDX and Next.js
I recently migrated this site to Next.js from Gatsby. I wish I had a good reason for this, but I don't.
The Gatsby site used MDX and I wanted to carry that over. Little did I know what a pain in the arse it is to get MDX working just so on Next.js.
I needed a setup that supported:
- frontmatter
- inlined components
- colocated images
- presentation templates
Next.js has @next/mdx
which supports inlined components, and I guess colocated images through imports, but not frontmatter. Also, presentation templates need to be imported inline which is ugly, IMO. I could live with the image imports, but having to import the same components into every MDX file that I needed was not optimal. It's nice that you can keep your mdx
files in pages/
though.
So onward to next-mdx-remote
. This supports frontmatter using grey-matter
, and you can pass common inline components through the template which you can set up through Next.JS dynamic routes. Ace. But no colocated images. In fact, images can only be served through public/
. I attempted to dynamically require
or import
them. I tried a variation on this solution to load the images, but kept getting runtime errors. Alas, etc.
Resolving that I didn't have the time to faff around with this just now (procrastination task), my solution was to use copy-webpack-plugin
and have webpack copy the images to public/
. I don't like it, but it works, and I can figure out if there's a nicer way of doing this later.
Loading...webpack: (config) => { config.plugins.push( new CopyPlugin({ patterns: [ { from: path.join(__dirname, 'path/**/*.jpg'), to: path.join(__dirname, 'public/[path][name][ext]'), }, ... ], }) ); }
Update the from
to point to where your colocated images will be, and the to
will match the directory structure in public/
. You also need to use path.join
or whatever you want to use to have the full path. Relative paths won't work here.
You'll now be able to use markdown image syntax to reference the image:
Loading...\![Image description](colocated-image.jpg)
This will generate an <img />
tag, which you can replace in your template by passing an img
component to MDXRemote
. You can replace it with next/image
or a custom image component. path
is a variable set to the path to the mdx
file.
Loading...const components = (path) => ({ img: (props) => { return <Image {...props} src={`/assets/${path}/${props.src}`} fill lazy />; } }); return ( <MDXRemote {...mdxSource} components={components(path)} /> );