Gatsby Logo

How to use gatsby-plugin-image for dynamic images

Andre L
5 min readNov 4, 2021

--

Over a year ago, I wrote a Medium post about how to use gatsby-image for dynamic images. Gatsby has since then deprecated gatsby-image and released a new plugin called gatsby-plugin-image.

The new plugin promises an easier API and performance benefits. It also splits the Image component into two separate components: StaticImage and GatsbyImage.

You can find an introduction to the new plugin API here. I will quickly go over the changes between gatsby-image and gatsby-plugin-image before I will show you how I adapted my image logic to work smoothly for a set of dynamic images.

Static Images

The old way

gatsby-image forced you to write a painful amount of boilerplate code - including a page-level GraphQL query - to render a simple locally-hosted static image.

import React from 'react';
import { graphql } from 'gatsby';
import Img from 'gatsby-image';
const Page = ({ data }) => (
<Img
fluid={data.file.childImageSharp.fluid}
alt="Portrait of Andre Landgraf"
/>
);
export default Page;export const query = graphql`
query ImageQuery {
file(relativePath: { eq: "portrait.jpg" }) {
childImageSharp {
fluid( maxWidth: 480) {
...GatsbyImageSharpFluid_withWebp
}
}
}
}`;

Notice that we address the image “portrait.jpg” by its relative pathname based on the base path from gatsby-source-filesystem.

Let’s say we have an image folder src/assets/images and our image is located in that folder, we would have specified our filesystem import in gatsby-config.js like so:

plugins: [
{
resolve: 'gatsby-source-filesystem',
options: {
path: `${__dirname}/src/assets/images/`,
name: 'images',
},
`gatsby-plugin-sharp`,
`gatsby-transformer-sharp`, // Needed for dynamic images
];

The new way

With gatsby-image-plugin, you can get rid of the GraphQL query and props parsing. Instead, you can just access the static image via the new StaticImage component.

import React from 'react';
import { graphql } from 'gatsby';
import { StaticImage } from 'gatsby-plugin-image';
const Page = ({ data }) => (
<StaticImage
src="../assets/images/portrait.jpg"
alt="Portrait of Andre Landgraf"
/>
);
export default Page;

Notice that we are now accessing the image via it’s relative path within the repository. If our page is located in /src/pages/ , we can now access the file src/assets/images/portrait.jpg as we would with any imports by navigating from the current file/folder to the target file.

Personal note: I don’t like how StaticImage uses the relative path within the repository instead of the relative path to the gatsby-source-filesystem base path. This makes copy-pasting code from one page to anothe really cumbersome. The lack of IntelliSense on the imports doesn’t make it any easier.

Dynamic Images

Anyways, this article is about dynamic images — images that are based on dynamic data.

For dynamic images, you cannot use page queries or the StaticImage component. This is where my previous Medium post comes into play. gatsby-image wasn’t super helpful for dynamic images…

The old way

My workaround for gatsby-image included adding the relative paths of the locally-hosted images to your dynamic data.

"article": { 
"title": “Working with Images in Gatsby”,
"content": ...
"coverImage": "blog/gatsby.jpg",
"coverImageAlt": "Gatsby Logo"
}

A global context used in wrapPageElement would query all images from once and offer the information to a custom Image component.

Context (the old one)

import React from 'react';
import { graphql, useStaticQuery } from 'gatsby';
const ImagesContext = React.createContext({ data: {} });function GatsbyImagesProvider({ children }) {
const data = useStaticQuery(graphql`{
allFile {
nodes {
relativePath
childImageSharp {
fluid(maxWidth: 1200) {
...GatsbyImageSharpFluid_withWebp
}
}
}
}
}`
);
const context = { data };
return (
<ImagesContext.Provider value={context}>
{children}
</ImagesContext.Provider>
);
}
export { ImagesContext, GatsbyImagesProvider };

Image component (the old one)

import React, { useContext, useMemo } from 'react';
import Img from 'gatsby-image';
import { ImagesContext } from '../../contexts/images';
const Image = ({src, alt}) => {
const { data } = useContext(ImagesContext);

const fluid = useMemo(
() => data.allFile.nodes
.find(({ relativePath }) => src === relativePath)
.childImageSharp.fluid,
[data, src]
);
return <Img fluid={fluid} alt={alt} />;
};
export default Image;

This way, I was able to access images for my dynamic data in page templates or across all components simply by using my custom Image component.

import Image from '../image/image'const BlogArticle = ({ article }) => (
<div>
<h1>{article.title}</h1>
<Image src={article.coverImage} alt={article.coverImageAlt} />
...
</div>
);

The new way

With gatsby-plugin-image, you can achieve the same logic by transforming your dynamic data image path attributes to GraphQL file objects.

At least that’s what the Gatsby Image documentation quickly mentions without going into detail how the gatsby-config.js or gatsby-node.js configurations would look like…

Until I have figured that out, I will keep using my gatsby-source-filesystem to dynamic image hack and now I want to show you how I adapted my previous logic to work with gatsby-plugin-image.

The updated gatsby-config.js

plugins: [
'gatsby-plugin-image',
'gatsby-plugin-sharp',
'gatsby-transformer-sharp',
{
resolve: 'gatsby-source-filesystem',
options: {
path: `${__dirname}/src/assets/images/`,
name: 'images',
},
},
];

The new React context

import React from 'react';
import { graphql, useStaticQuery } from 'gatsby';

const ImagesContext = React.createContext({ data: {} });

function GatsbyImagesProvider({ children }) {
const data = useStaticQuery(graphql`{
allFile(filter: { sourceInstanceName: { eq: "images" } }) {
nodes {
relativePath
childImageSharp {
gatsbyImageData(layout: FULL_WIDTH)
}
}
}
}`);
const context = { data }; return (
<ImagesContext.Provider value={context}>
{children}
</ImagesContext.Provider>
);
}export { ImagesContext, GatsbyImagesProvider };

Notice that we are filtering all files for all images based on our gatsby-source-filesystem query to boost performance.

The new Image component

import React, { useContext, useMemo } from 'react';
import { GatsbyImage } from 'gatsby-plugin-image';
import { ImagesContext } from '../../contexts/ImagesContext';
const Image = ({ src, alt }) => {
const { data } = useContext(ImagesContext);
const fluid = useMemo(
() => data.allFile.nodes
.find(({ relativePath }) => src === relativePath)
.childImageSharp.gatsbyImageData,
[data, src],
);
return <GatsbyImage image={fluid} alt={alt} />;
};

export default Image;

You can see that the Image context and component did not change by much. The interface for the image component did not change at all!

Access images based on dynamical data like before:

import Image from '../image/image'const BlogArticle = ({ article }) => (
<div>
<h1>{article.title}</h1>
<Image src={article.coverImage} alt={article.coverImageAlt} />
...
</div>
);

=> no changes to the interface!

Further considerations

Using the context certainly increases the build-time, especially if we host a bunch of images in our file system. However, so far, I didn’t notice any significant drops in performance on runtime.

I am pretty sure that the better alternative going forward will be to parse the relative paths in the dynamic data to GraphQL file objects. This change will require a plugin or changes to the gatsby-node.js and then to the dynamic data GraphQL queries as hinted in the Gatsby Image documentation.

It’s great to see that Gatsby created ways to better manage dynamic images. I am excited to see how my code will evolve further over time!

--

--

Andre L

Hey there, thanks for checking by! 👋 I am a fullstack developer and tech enthusiast and I cannot get enough of React, JavaScript, web, voice UIs, and more!