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 🕺