MDX Embedded Images with the All-New Gatsby Image Plugin

Paul Scanlon
Paul Scanlon
April 12th, 2021

Gatsby 4 Update!

November 10th, 2021

Hello all, I noticed when I upgraded this demo repo to Gatsby 4 there were one or two small issues with this approach, I’ve now updated the code examples and explanations below to work with Gatsby 4.

— end update —

 

This post takes a deep dive into MDX frontmatter and how it can be used to store references to either local and / or remote images and render them anywhere in the MDX body using GatsbyImage from the new gatsby-plugin-image.

Check out the demo site 👉  https://gatsbymdxembeddedimages.gatsbyjs.io/

Check out the code repo 👉 https://github.com/PaulieScanlon/gatsby-mdx-embedded-images

The new gatsby-plugin-image is  absolutely brilliant to use when returned by JSX. Unfortunately gatsby-plugin-image doesn’t work in quite the same way when returned by MDX.

TL;DR: it doesn’t render an image 😖

There’s no problem with gatsby-plugin-image itself! The situation has more to do with how image data is processed, queried and then referenced when returned by MDX. Let’s see how to fix it!

The general approach

The idea behind this approach is to store references to images or image URLs in the MDX frontmatter , which can later be processed by gatsby-transformer-sharp and used anywhere around the post body. By using <GatsbyImage /> from gatsby-plugin-image it’s possible to maintain blazing fast site speed with multiple images found in any MDX post or page body!

Note: you can of course use an HTML <img … /> tag in your MDX. But the images won’t be automatically optimized and you won’t get the cool blur up effect that you get when using gatsby-plugin-image

Local Images

References to images stored on disk and co-located with the MDX file can be stored in frontmatter.

Example of frontmatter with reference to local images:

Example of image co-location on disk:

Remote Images

If you are working with images  images stored on a remote URL, references to these remote images can also be stored in frontmatter.

Example of  frontmatter with reference to remote images:

Using GatsbyImage

The docs will explain that to use <GatsbyImage /> from gatsby-plugin-image in JSX, you can do something similar to the code example below. However, as mentioned previously, this will only work if the data passed on to <GatsbyImage /> has been processed and queried in the correct way. 

Typically, when using locally sourced image files (files on disk), Gatsby and GraphQL are able to correctly infer the type and process the data using childImageSharp  from gatsby-transformer-sharp. Happily, this will work just fine when combined with the getImage helper function.

Example of using <GatsbyImage /> in JSX:

However, when querying image URLs from MDX frontmatter Gatsby and GraphQL need a little help inferring the type.

One possible solution

One way round the problem is to process the frontmatter fields on the server using gatsby-node.js then pass the gatsbyImageData  back to MDX using the <MDXRenderer /> from gatsby-plugin-mdx and apply it to  <GatsbyImage /> via data made available on a custom prop 🥴

… and the usage for this approach would look a something like this:

Example of using <GatsbyImage /> in MDX :

GraphQL Type Inference

This is a bit of a brain bender but… in order to correctly process images using childImageSharp, GraphQL needs to understand that the field is of type File.

For the Local Image example, the typeDefs created by Gatsby will automatically and correctly infer that image1.jpg is in fact of type File.

Example of GraphQL type inference for images as files:

But! That said…

For the Remote Image example, the typeDefs created by Gatsby will infer that the image URLs are of type String!🤯

Example of GraphQL type inference for images stored on a remote URLs:

Can you see the problem???? We can’t process a String withchildImageSharp!

To correct this, createTypes can be used to manually Type a new field and store it using the same name on the parent MDX node.  I create a new field of the same name to avoid directly mutating the original node on frontmatter

Code snippet of createTypes:

Here, createTypes sets embeddedImagesRemote to type [File] and sets a reference @link for the field name provided when using createNodeField.

CreateRemoteFileNode and CreateNodeField

In this demo the, image URLs are stored on Cloudinary and won’t exist as Files on disk until they have been remotely sourced and stored as a Node in the Gatsby data layer. 

Using Promise.all and Array.prototype.map to iterate over the embeddedImagesRemote from frontmatter, it’s then possible to source the remote image using  createRemoteFileNode for each image URL and once sourced, create a new node field using createNodeField that represents the image as a File.

Example of onCreateNode function that uses createRemoteFileNode and createNodeField method:

This will result in the ability to query embeddedImagesRemote from GraphQL and see all the returned gatsbyImageData as you normally would — just as though you were using local files!

Example of GraphQL allMdx query:

gatsby-plugin-sharp config

Before going too much further, it’s a good idea to configure gatsby-plugin-sharp and set up some “global” options. This will save duplicating some of the settings in the page query.

Example of setting global config options for gatsby-plugin-sharp:

Available Data

Now that the remote image data is available in Gatsby’s data layer, you can work with it in the usual way — e.g., via a page query or by using useStaticQuery.

For the purposes of this demo all data is queried using a page query.

Example of page query:

MDX Renderer

Once the image data has been correctly processed, it can be passed back on to the <MDXRenderer />  via named props. In this example these are called remoteImages  and localImages .

Usage

The final step is to use the props that have been passed on to the <MDXRenderer /> in the actual .mdx file and use them with the <GatsbyImage /> component and the getImage helper function.

Here’s an example of how to use the <GatsbyImage /> component with remotely sourced images from frontmatter:

Don’t forget to check out the demo site for this exploration, clicking through to both Remote Images and Local Images pages. All the source code for this demo can be seen in the repo.  Have any questions or comments, I’m always available on Twitter for a natter.

Ttfn,

Paul 🕺

Paul Scanlon
Written by
Paul Scanlon

After all is said and done, structure + order = fun! Senior Software Engineer (Developer Relations) for Gatsby

Follow Paul Scanlon on Twitter

Talk to our team of Gatsby Experts to supercharge your website performance.