Gatsby Icon
Gatsby Logo

How to use gatsby-image for dynamic images

Andre L
5 min readApr 29, 2020

--

Note: gatsby-image has been deprecated in favor for gatsby-plugin-image.

I updated my code base accordingly and wrote a new Medium post about my new implementation here: How to use gatsby-plugin-image for dynamic images

I have been using S3 buckets for the longest time to store images for my webpages. Getting up and running is just too easy, you create a S3 bucket on AWS, upload the assets (e.g. images), set Cache-Control to a high enough value, and mark the asset as public. You will have an URL to your image in no time. When I first looked at gatsby-image, I was a bit overwhelmed by its complexity and turned it down to focus on building my MVPs.

Unfortunately, there is much more depth to it and S3 won’t help you with the more complex stuff. Image preprocessing, serving the right image size based on the user device, serving more performant formats (e.g. webp) and other optimisations can save your clients a lot of bandwidth and therefore also drastically increase the performance of page loading. Also, maybe you want to have a base64 representation as a lowSrc to have a blurry version of your image while your actual image is still loading. Doing this with S3 would require you to spend a lot of time cropping, processing and managing different versions of your image and utilising the html picture tag.

Luckily, Gatsby provides powerful tools to help us out!

Using gatsby-image for one static image

gatsby-image is handling all that by utilising the gatsby-transformer-sharp gatsby-plugin-sharp plugins. The general idea is to store your images together with your website and let gatsby and webpack take care of optimising the images. You will access the images client side using graphql and the gatsby-image Img component. It looked pretty complex in the beginning but I found the documentation to be very helpful.

Querying for one image to display on a page would look like so:

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

Quite a bit of code but keep in mind, this will serve the image portrait.jpg in the right size, as webp if the browser supports it, displaying a base64 blurry version until the image has been loaded and much more. For a more complete guide, check out the gatsby-image documentation.

Using gatsby-image for a set of images

Imagine you have an online store. Most likely, you want each item to have an image associated to it. Therefore, you store the relative path of those images inside your database as part of each item object.

"item":  {   
"name": "Toilet Paper",
"price": "200",
"src": "items/tp.jpg",
"alt": "Beautiful soft toilet paper"
}

Now you might utilise gatsby-node.js to create a details page for each item by fetching all items at build time and using the createPages-function. This way the picture src path will be known at build time. Taking the example from above, it is just logically that you might want to do something like this:

export const query = graphql`
query ImageQuery {
file(relativePath: { eq: ${item.src}) {
childImageSharp {
fluid( maxWidth: 480) {
...GatsbyImageSharpFluid_withWebp
}
}
}
}`;

Disclaimer: This won’t work!

Gatsby doesn’t support string interpolation on graphql queries. There are a lot of open discussion on GitHub and Twitter about it [1, 2, 3] and it goes quite in depth but the main reasoning is that static queries are parsed once during build time (server-side). Client-side, those variables would be frozen. How would you make sure that this is communicated to the developers? How would you debug this? The new mental model required would be very complex.

For our image queries this means: If you want to query images on dynamically created pages, you have to query all images and filter by relative path. Since, I have had a hard time to find an implementation that I liked, I implemented a simple reactjs context/component to make it as accessible as possible.

The ImagesContext

Instead of querying separately on each page, we write one query to get us all file objects at once and store them inside our context.

import React from 'react';
import PropTypes from 'prop-types';
import { graphql, useStaticQuery } from 'gatsby';
const ImagesContext = React.createContext({ data: {} });function GatsbyImagesProvider({ children }) {
const data = useStaticQuery(graphql`
query {
allFile {
nodes {
relativePath
childImageSharp {
fluid(maxWidth: 1200) {
...GatsbyImageSharpFluid_withWebp
}
}
}
}
}`
);
const context = { data }; return (
<ImagesContext.Provider value={context}>
{children}
</ImagesContext.Provider>
);
}
GatsbyImagesProvider.propTypes = {
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]).isRequired,
};
export { ImagesContext, GatsbyImagesProvider };

This context will hold all images and their relative path and will wrap the page element to ensure that its state will be persistent between all page changes.

import React from 'react';
import { GatsbyImagesProvider } from './src/contexts/ImagesContext';
wrapPageElement = ({ element, props: { location } }) => (
<GatsbyImagesProvider>
{element}
</GatsbyImagesProvider>
);
export default wrapPageElement;

Have a look at the gatby-browser api for more information.

The Image component

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 component utilises our new ImagesContext to match the desired image by its relative path and return the gatsby-image Img component.

How to use it in your component

const ItemDetails = ({ data: { items: item } }) => (
<div>
<h1>{item.name}</h1>
<Image src={item.src} alt={item.alt} />
</div>
);

I like the context/component implementation quite a lot and I hope you might find it useful, too.

I developed a Gatsby skeleton application that is running this implementation. You can check it out on GitHub or visit the demo to see it in action.

Any feedback is highly appreciated.

Stay safe & happy coding ❤

--

--

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!