7

I am making a blog with next-mdx-remote & want to use images in the .mdx file outside of the public/ folder.

Here's the complete code of my blog project → https://github.com/deadcoder0904/blog-mdx-remote

I have the following folder structure:

.
├── package-lock.json
├── package.json
├── pages
│   ├── _app.js
│   ├── blog
│   │   └── [slug].js
│   ├── dark.css
│   ├── index.js
│   └── new.css
├── posts
│   ├── blog
│   │   ├── hello-world
│   │   │   ├── Rustin_Cohle.jpg
│   │   │   └── index.mdx
│   │   └── shit-world
│   │       └── index.mdx
│   └── tutorials
│       └── console-log-in-javascript
│           └── index.mdx
└── utils
    └── mdxUtils.js

I have all my content in posts/ folder.

I have 2 folders in it: blog/ & tutorials/

Each post is in their own folder inside of blog/ or tutorials/ & every one of those folder contain images that are used in that particular post.

For example, in the hello-world folder, there is 1 image named Rustin_Cohle.jpg.

I want to use this image in hello-world/index.mdx file but I'm unable to do it.

I can't use import or require as it's a limitation of next-mdx-remote.

I tried using a custom component called Image that used img underneath & passed it to hydrate but it didn't work either.

components/Image.js

export const Image = ({ src, alt }) => (
    <img style={{ backgroundColor: 'red' }} src={src} alt={alt} />
)

pages/blog/[slug].js

import hydrate from 'next-mdx-remote/hydrate'
import { Image } from '../components/Image'

const components = { Image }

const Blog = ({ source, frontMatter }) => {
  const content = hydrate(source, { components })
  return (
    <div>
      <h1>{frontMatter.title}</h1>
      {content}
    </div>
  )
}

The following MDX file uses the above Image component as passed through hydrate.

hello-world/index.mdx

---
title: Hello World
date: '2019-09-06T14:54:37.229Z'
tags: ['hello', 'world']
author: John Doe
description: This is the first post
---

Hey this is my first post

![Rustin Cohle](./Rustin_Cohle.jpg)

<img src="./Rustin_Cohle.jpg" alt="Rust Cohle" />

<Image src="./Rustin_Cohle.jpg" alt="Rust Cohle" />

This is the end of the first post

I even tried using MDXProvider & it didn't work either.

pages/index.js

import { MDXProvider } from '@mdx-js/react'

const components = { Image }

const HomePage = ({ posts }) => {
    return (
        <MDXProvider components={components}>
         ...
        </MDXProvider>
    )
}

How do I use images then? I want them to be only in the particular post's folder like hello-world blog would contain its images only in hello-world/ folder.

deadcoder0904
  • 7,232
  • 12
  • 66
  • 163
  • Check out the [following answer](https://stackoverflow.com/a/70917295/6908282) if you're using `mdx-bundler`. – Gangula Jan 30 '22 at 17:51

3 Answers3

8

I was able to load images in this scenario. My idea was to use webpack file-loader to accomplish this and add more info to components mapping telling where the current post is placed (note the components function).

My page [slug].js is like this:

import renderToString from 'next-mdx-remote/render-to-string'
import hydrate from 'next-mdx-remote/hydrate'
import matter from 'gray-matter'

import { getAllPostSlugs, getPostdata } from '../lib/posts'

const components = (slug) => ({
  h1: ({ children }) => <h1>{children}</h1>,
  img: ({ src, alt }) => {
    return (
      <p>
        <img
          alt={alt}
          src={require('../content/' + slug + '/' + src).default}
        />
      </p>
    )
  }
})

const Post = ({ source, frontMatter, slug }) => {
  const content = hydrate(source, {
    components: components(slug)
  })
  return (
    <>
      <h1>Post</h1>
      <p>{content}</p>
    </>
  )
}

export async function getStaticPaths() {
  const paths = getAllPostSlugs()
  return {
    paths,
    fallback: false
  }
}

export async function getStaticProps({ params }) {
  const postContent = await getPostdata(params.slug)
  const { data, content } = matter(postContent)
  const mdxSource = await renderToString(content, {
    components: components(params.slug),
    scope: data
  })
  return {
    props: {
      slug: params.slug,
      source: mdxSource,
      frontMatter: data
    }
  }
}

export default Post

And my next.config.js:

module.exports = {
  webpack: (config, options) => {
    config.module.rules.push({
      test: /\.(svg|png|jpe?g|gif|mp4)$/i,
      use: [
        {
          loader: 'file-loader',
          options: {
            publicPath: '/_next',
            name: 'static/media/[name].[hash].[ext]'
          }
        }
      ]
    })
    return config
  }
}

Ps: it works like a charm with next/image :)

Tulio Faria
  • 874
  • 7
  • 16
  • Damn that's a neat trick, have my upvote. However, I do think I should not mark this as answer as this seems like a hack but it works so I'll give an upvote :) – deadcoder0904 Jan 12 '21 at 11:44
  • Thank you! We are going to use this approach during the migration of a Gatsby project to NextJS. It´s going to help keep the content the same :) – Tulio Faria Jan 12 '21 at 15:17
  • 2
    Hi there - author of next-mdx-remote here. Just dropping in to say this is a really cool idea and implementation. Great work Tulio! – Jeff Escalante Jan 12 '21 at 16:11
  • Hey @JeffEscalante should I mark this one as an answer? – deadcoder0904 Jan 13 '21 at 05:48
  • @JeffEscalante @TulioFaria Is it possible to also `import` react components inside mdx file? It's the only thing which is stopping me to switch to `next-mdx-remote` from `next-mdx-enhanced`. All my mdx files are local. – GorvGoyl Apr 12 '21 at 21:30
  • Somehow this approach isn't working for me. I get this error: `TypeError: unsupported file type: undefined (file: undefined)` – Ben Sep 21 '21 at 22:03
3

It's not possible unfortunately as next-mdx-remote treats the markdown content as data & doesn't pass through Webpack at all :(

deadcoder0904
  • 7,232
  • 12
  • 66
  • 163
  • 4
    Hi, I'm the author of next-mdx-remote -- just confirming this is true. My recommendation is to use images in the `public` folder. – Jeff Escalante Nov 04 '20 at 18:40
2

Note that starting with Next.js v11, you can import images from any directory into a mdx file without any additional config - Reference 1 & Reference 2

Gangula
  • 5,193
  • 4
  • 30
  • 59