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
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
References to images stored on disk and co-located with the MDX file can be stored in
Example of frontmatter with reference to local images:
Example of image co-location on disk:
If you are working with images images stored on a remote URL, references to these remote images can also be stored in
Example of frontmatter with reference to remote images:
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
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
For the Local Image example, the typeDefs created by Gatsby will automatically and correctly infer that
image1.jpg is in fact of type
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
Example of GraphQL type inference for images stored on a remote URLs:
Can you see the problem???? We can’t process a
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
Code snippet of
embeddedImagesRemote to type
[File] and sets a reference
@link for the field name provided when using
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
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.
onCreateNode function that uses
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
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
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
For the purposes of this demo all data is queried using a page query.
Example of page query:
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
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
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.