In this chapter, you will master the art of adding images to your Gatsby site. First, we will learn a little about the history of images on the web, before understanding why importing images is not as easy you might think. We will then move on to creating images that progressively load in and are performant.
In this chapter, we will cover the following topics:
To complete this chapter, you will need to have completed Chapter 3, Sourcing and Querying Data (from Anywhere!).
The code for this chapter can be found at https://github.com/PacktPublishing/Elevating-React-Web-Development-with-Gatsby-4/tree/main/Chapter05.
When was the last time you visited a website without any images? You might be thinking that this is a hard question to answer. Images are a critical part of websites and our browsing experience. We use images for logos, products, profiles, and marketing to convey information, entice, or excite through a visual medium. While images are great for these use cases (and many more!), they are the single largest contributor to page size. According to the HTTP Archive (https://httparchive.org/), the median page size on desktops is 2,124 KB. Images make up 973 KB of this size, which is roughly 45% of the total page size.
As images are so integral to our browsing experience, we cannot do away with them. But when they account for so much of the page size, we should be doing everything in our power to ensure that they are optimized, accessible, and as performant as possible. Newer versions of browsers (including Chrome) have responsive image capabilities built into them. Instead of providing a single source for the image, the browser can accept a source set. The browser uses this set to load an image at a different size, depending on the size of the device. This ensures the browser never loads images that are too big for the space available. Another way in which developers can optimize images, specifically in React, is with lazy loading. Lazy loading is the process of deferring the load of your images until a later point in time. This could be after the initial load, or when they specifically become visible on the screen. Using this technique, you can improve your site's speed, better utilize a device's resources, and reduce a user's data consumption.
Manually creating high-performance sites that contain lazy-loaded images is a project in itself. Luckily, Gatsby has a plugin that takes the pain away when you're generating responsive, optimized images – gatsby-plugin-image.
This plugin contains two React image components with specific features aimed at creating a better user experience when using images, both for the developer and the site visitor:
In the next section, we will begin looking at the first of the two image components in this chapter – the StaticImage component.
StaticImage is best used when an image will always remain the same. It could be your site logo, which is the same across all pages, or a profile photo that you use at the end of blog posts, or a home page hero section, or anywhere else where the image is not required to be dynamic.
Unlike most React components, the StaticImage component has some limitations on how you can pass the props to it. It will not accept and use any of its parents' props. If you are looking for this functionality, you will want to use the GatsbyImage component.
To get an understanding of how we utilize the StaticImage component, we will implement an image on the hero of our home page:
npm install gatsby-plugin-image gatsby-plugin-sharp
gatsby-source-filesystem
These dependencies spawn other node processes and may take a little longer to install compared to our previous installs.
plugins: [
'gatsby-plugin-image',
'gatsby-plugin-sharp',
'gatsby-transformer-sharp',
// Other plugins
],
import { StaticImage } from "gatsby-plugin-image";
<StaticImage
src="../../assets/images/sample-photo.jpeg"
/>
The src prop should be the relative path to the image from the current file, not the root directory.
<StaticImage
src="../../assets/images/sample-photo.jpeg"
alt="A man smiling"
placeholder="tracedSVG"
layout="fixed"
width={400}
height={600}
/>
Let's look at these props in detail:
Tip
StaticImage can also take a remote source as its src prop. Any images that are specified as URLs will be downloaded at build time and then resized. Using remote images instead of local images is a great way to keep your repository small, but it should also be remembered that Gatsby does not know when that image changes if it is outside of your repository. If the image is changed on the remote server, it will only update when you rebuild your project.
Now that we understand how to utilize the StaticImage component, let's turn our attention to the other half of gatsby-plugin-image and learn about the GatsbyImage component.
If ever you need to use dynamic images, such as those embedded in your Markdown content, then you can use the GatsbyImage component.
Let's add hero images to our Markdown/MDX blog posts using the GatsbyImage component:
npm install gatsby-transformer-sharp
{
resolve: 'gatsby-source-filesystem',
options: {
path: '${__dirname}/assets/images',
},
},
Unlike StaticImage, GatsbyImage requires that images are ingested into our data layer. We can use the gatsby-source-filesystem plugin to achieve this, but by giving it the path to our images.
---
type: Blog
title: My First Hackathon Experience
desc: This post is all about my learnings from my
first hackathon experience in London.
date: 2020-06-20
hero: ../../assets/images/cover-1.jpeg
tags: [hackathon, webdev, ux]
---
Here, you can see an updated example of the placeholder Markdown with a hero key added. Be sure to replace the relative path in this example with the one to your image.
import { GatsbyImage, getImage } from "gatsby-plugin-
image";
export const pageQuery = graphql'
query($slug: String!) {
blogpost: markdownRemark(fields: { slug: { eq:
$slug } }) {
frontmatter {
date
title
tags
hero {
childImageSharp {
gatsbyImageData(
width: 600
height: 400
placeholder: BLURRED
)
}
}
}
html
}
}
';
You may notice that the data that's passed to the gatsbyImageData function looks very similar to the props of the StaticImage component that we saw in Step 8 of the previous section. In this instance, we are using the BLURRED placeholder for the image, which uses a blurred, lower-resolution version of the image in place of the original image while it is loading. We are now able to retrieve the hero data from the component as it is included in the page query.
const {
blogpost: {
frontmatter: { date, tags, title, hero },
html,
},
} = data;
const heroImage = getImage(hero)
First, retrieve hero from the data prop, and then use the getImage utility from gatsby-plugin-image to retrieve the image data that's required to render it and assign it to a const.
<GatsbyImage image={heroImage} alt="Your alt text" />
Use the const defined in Step 7 to render the image within a GatsbyImage component. Be sure to provide it with alt text to keep your image accessible – you could provide this via frontmatter as well if you wish.
Further Exercise
We've learned how to add images to our blog pages, so why not use what you have learned to add a smaller version of the same image to the blog preview page? An implementation can be found in this book's GitHub repository (https://github.com/PacktPublishing/Elevating-React-Web-Development-with-Gatsby-3/tree/main/Chapter05) if you want to see how it can be achieved.
You might find yourself adding the same configuration to your images across the whole site. Let's find out how we can use the defaults to keep our code in Don't Repeat Yourself (DRY) form.
To create a consistent look and feel, you may have included the same props with many instances of the two image components. Keeping these image components updated can be a monotonous task if your site is image-heavy. Instead, you could configure the defaults within the options of gatsby-plugin-sharp:
{
resolve: 'gatsby-plugin-sharp',
options: {
defaults: {
formats: ['auto', 'webp'],
placeholder: 'blurred'
quality: 70
breakpoints: [300…]
backgroundColor: 'transparent'
tracedSVGOptions: {}
blurredOptions: {}
jpgOptions: {}
pngOptions: {}
webpOptions: {}
avifOptions: {}
}
}
},
Let's look at each of these options in detail:
We now have a good understanding of the gatsby-plugin-image package. There are, however, some important niches to using this package with other sources such as Content Management Systems (CMSes) – let's take a look.
It is not always practical to store images within your repository. You may want someone other than yourself to be able to update or add images to your site without you needing to change the code. In these cases, serving images via CMS is preferable. It's still important that we use the Gatsby image plugin as we want our images to be performant, regardless of the source. To understand how we would integrate images via CMS, let's use an example. Let's add a profile image to our about page using gatsby-plugin-image and a CMS.
Important Note
Due to the vast number of headless CMSes in the market, we will continue to focus on the two mentioned in the Sourcing data from a Headless CMS section of Chapter 3, Sourcing and Querying Data (from Anywhere!): GraphCMS and Prismic.
Both of the following sections will assume you have already installed the CMS's dependencies and integrated the CMS via your gatsby-config.js file. Please only implement one of the following.
By making some small modifications to our configuration and queries, we can ensure that we can source images from GraphCMS that utilize gatsby-plugin-image and load in on the site in the same way as those that are locally sourced:
{
resolve: 'gatsby-source-graphcms',
options: {
endpoint: process.env.GRAPHCMS_ENDPOINT,
downloadLocalImages: true,
},
},
By modifying your gatsby-source-graphcms plugin's options to include the downloadLocalImages option, the plugin will download and cache the CMS's image assets within your Gatsby project.
export const query = graphql'
{
markdownRemark(frontmatter: { type: { eq: "bio" } }) {
html
}
graphCmsAsset(fileName: { eq: "profile.jpeg" }) {
localFile {
childImageSharp {
gatsbyImageData(layout: FULL_WIDTH)
}
}
}
}
';
As with the local queries we looked at in the The GatsbyImage component section, we are using gatsbyImageData within our query and can make use of any of the configuration options that it supports. Here, we are specifying that the image should be full width.
import { GatsbyImage, getImage } from "gatsby-plugin-
image";
const {
markdownRemark: { html },
graphCmsAsset: { localFile },
} = data;
const profileImage = getImage(localFile);
First, retrieve graphCmsAsset from the data prop and then use the getImage utility from gatsby-plugin-image to retrieve the image data that's required to render it. Finally, assign it to a const called profileImage.
return (
<Layout>
<div className="max-w-5xl mx-auto py-16 lg:py-24
text-center">
<GatsbyImage
image={profileImage}
alt="Your alt text"
className="mx-auto max-w-sm"
/>
<div dangerouslySetInnerHTML={{ __html: html
}}></div>
</div>
</Layout>
);
Use the const parameter that we defined in Step 8 to render the image within a GatsbyImage component. Be sure to provide it with alt text to keep your image accessible – you could provide this via frontmatter as well if you wish.
Now that we understand how we can source images within GraphCMS, let's turn our attention to how the same can be achieved in Prismic.
With a few simple changes to our configuration and queries, we can source images from Prismic utilizing gatsby-plugin-image and load them in on the site in the same way as local images:
{
resolve: "gatsby-source-prismic",
options: {
repositoryName: "elevating-gatsby",
schemas: {
icebreaker:
require("./src/schemas/icebreaker.json"),
profile:
require("./src/schemas/profile.json"),
},
shouldDownloadFiles: () => true,
},
},
Add the new scheme to schemas that we added in Step 6. We will also add the shouldDownloadFiles option. This is a function that determines whether to download images. In our case, we always want it to download images so that we can use gatsby-plugin-image, and therefore set the function to always return true.
import { GatsbyImage, getImage } from "gatsby-plugin-
image";
export const query = graphql'
{
markdownRemark(frontmatter: { type: { eq: "bio" }
}) {
html
}
prismicProfile {
data {
photo {
localFile {
childImageSharp {
gatsbyImageData(layout: FULL_WIDTH)
}
}
}
}
}
}
';
We utilize gatsbyImageData within our query and can make use of any of the configuration options that it supports. Here, we are specifying that the image should be full width.
const {
markdownRemark: { html },
prismicProfile: {
data: {
photo: { localFile },
},
},
} = data;
const profileImage = getImage(localFile);
First, retrieve the prismicProfile data from the data prop and then use the getImage utility from gatsby-plugin-image to retrieve the image data that's required to render it and assign it to a const called profileImage.
<GatsbyImage
image={profileImage}
alt="Your alt text"
className="mx-auto max-w-sm"
/>
Use the const parameter defined in Step 12 to render the image within a GatsbyImage component. Be sure to provide it with alt text to keep your image accessible – you could provide this via frontmatter as well if you wish.
You should now feel comfortable using images and sourcing images via the Prismic CMS. While we have only looked at two CMSes, these concepts can be taken and applied to any headless CMS that supports images. Now, let's summarize what we have learned in this chapter.
In this chapter, we learned about images on the web, and how critical they are to our browsing experience. We learned about gatsby-plugin-image and implemented the two contained image components. We also learned in which circumstances to use which component.
At this stage, you should feel comfortable developing all manner of pages for your Gatsby site. In the next chapter, we will begin to look at how we can take our development site live. We will start this journey by looking at search engine optimization.