Working with images in Gatsby

Utilizing the latest, more flexible, and straightforward API

December 24th 20207 minutes read

Working with images is boring. If you were to include an image on a website, you have to assure:

  • That the images are properly resized for each screen
  • That we serve the right image based on the device pixel density
  • That we serve modern image formats when possible
  • That the images are compressed

But also..

  • That we don't load all the page at once, consuming bandwidth, when the visitor might not even scroll to see most of the pages
  • That we don't cause layout movements, as the images abruptly load

Thankfully, Gatsby offers some nice utilities, bundling all the steps required into a single tool-chain, letting us focus on other things.

To use GraphQL or not?

Up to recently, if you didn't want to go the GraphQL route, you were all out of luck. Thankfully, now it's possible with the help of the StaticImage component. But let's take a step back for a moment.

Gatsby is a fantastic framework if you want to consume data from various remote sources. Fetch the data (instagram, wordpress, etc), push them into the GraphQL layer, and populate your pages based on that data. To help with the images that these sources include, the Gatsby team & collaborators added a set of utilities to optimize them. But for everything outside the GraphQL layer, there wasn't anything.

That resulted in an unnecessary boilerplate. If you had a simple local image but wanted a cool blur effect, you had to go through GraphQL. That said, with the latest gatsby-plugin-image we have the solution, and we can safely go one way or the other depending on the use case.

  1. If the image comes from a remote source, you're already using GraphQL
  2. If the image is part of a collection, like the cover of a blog post, you will have an easier time with GraphQL
  3. If the image is the 404 illustration, the image of a section, or something ephemeral, inlining is the best way to go.

Inlining images

If your images don't go through the Gatsby GraphQL layer, you can use StaticImage

import { StaticImage } from 'gatsby-plugin-image';

// my actual 404
return (
  <StaticImage
    alt="Man looking on the map"
    className="border-b-4 border-gray-200"
    layout="constrained"
    width={400}
    src="../images/four-oh-four.png"
  />
);

The same configuration object that we declare in the GraphQL schema, can be passed as props. Rejoice.

GraphQL Configuration

Dependencies

First of all, we need to ensure we have all the dependencies in place.

We need 4 plugins before we start:

// src/gatsby-config.js
{
  resolve: `gatsby-source-filesystem`,
  options: {
    path: `${__dirname}/src/images`,
    name: 'images',
  },
  `gatsby-plugin-sharp`,
  `gatsby-transformer-sharp`,
  `gatsby-plugin-image`,
},

Types of images

Now, this is where it gets interesting - we can have three types of responsive images.

  1. Images with fixed width. When knowing exactly how big the images should be. (FIXED)
  2. Images that stretch across their fluid parent container. Completely dependent on their parent, who can take many shapes and forms between screen sizes. (FULL_WIDTH)
  3. Images that stretch across their container but limited to a maximum width (CONSTRAINED)

Ok, this might be confusing. The difference between FULL_WIDTH & CONSTRAINED can be seen in the following table. The FULL_WIDTH image will expand to fill its container, even if it looks blurred.

FULL_WIDTH constrained

So assuming we want a CONSTRAINED image and we don't want image copies bigger than 200px, here's our query.

query {
  image: file(relativePath: { eq: "image.jpg" }) {
    childImageSharp {
        gatsbyImageData(
          quality: 90
          width: 200
          layout: CONSTRAINED
        )
      }
    }
  }
}

This setting will make sure to include copies for both jpg and webp, even if we don't specifically request for the latter.

Placeholders

Everything is in order, but we probably want a smooth fallback. Here are our options:

  • BLURRED: (default) a blurred, low-resolution image, encoded as a base64 data URI
  • TRACED_SVG: a low-resolution traced SVG of the image
  • DOMINANT_COLOR: a solid color, calculated from the dominant color of the image.
  • NONE: no placeholder. Looks better with the background prop set.

Here are the first three options side to side. I tend to prefer the first two.

Blurred Dominant Traced
blurred dominant traced

Transforms

Gatsby allows us to do some transforms too:

  • grayscale
  • duotone
  • rotate
  • trim
  • cropFocus
  • fit

Frankly, Gatsby is a bit notorious for its build times, so I would advise to do a pre-processing to avoid transforming the same images again and again. If that's not possible, this option is for you.

Consuming the images

Contrary to the StaticImage example, GatsbyImage accepts an image prop.

import { StaticImage } from 'gatsby-plugin-image';

return (
  <GatsbyImage
    alt={album.title}
    image={album.cover.childImageSharp.gatsbyImageData}
    className="shadow"
  />
);

Now writing album.cover.childImageSharp.gatsbyImageData is a bit tedious, so we can import getImage from the very same package, and refactor it as follows:

import { StaticImage, getImage } from 'gatsby-plugin-image';

return (
  <GatsbyImage
    alt={album.title}
    image={getImage(album.cover)} // much better
    className="shadow"
  />
);

Referencing images

You probably want to fetch the images dynamically, as part of another entity. The best way for that is to link the images with the rest of the metadata. Here's an example from my blog.

  {
    "artist": "Joy Division",
    "title": "Unknown Pleasures",
    "releasedDate": 1979,
    "rating": 5,
    "cover": "./images/unknown-pleasures.jpg",
    "spotify": "https://open.spotify.com/album/0cbpcdI4UySacPh5RCpDfo"
  },
  {
    "artist": "King Crimson",
    "title": "In the Court of the Crimson King",
    "releasedDate": 1969,
    "rating": 5,
    "cover": "./images/in-the-court.jpg",
    "spotify": "https://open.spotify.com/album/5wec5BciMpDMzlEFpYeHse"
  },
  {
    "artist": "Radiohead",
    "title": "OK Computer",
    "releasedDate": 1997,
    "rating": 5,
    "cover": "./images/ok-computer.jpg",
    "spotify": "https://open.spotify.com/album/7dxKtc08dYeRVHt3p9CZJn"
  },

And the call with the rest of the metadata

export const query = GraphQL`
  {
    albums: allAlbumsJson(
      sort: {
        fields: [rating, badge, title, artist, releasedDate]
        order: DESC
      }
    ) {
      edges {
        node {
          id
          releasedDate
          title
          artist
          rating
          badge
          spotify
          cover {
            childImageSharp {
              gatsbyImageData(
                height: 200
                width: 200
                quality: 100
                layout: CONSTRAINED
                placeholder: DOMINANT_COLOR
              )
            }
          }
        }
      }
    }
  }
`;

Images in markdown files

Unfortunately, gatsby-plugin-image doesn't help with markdown files. We can optimize any images we query along with our data, but the images which are referenced inside the markdown file, won't be touched.

In order to do this, we have to include a separate plugin gatsby-remark-images. Now to keep things tidy, I like to keep my blog images near the markdown file, and copy them over with gatsby-remark-copy-linked-files.

Here's how it looks

blog-files

And here how it's written

Here's how it looks.

![blog-files](./blog-files.jpg)

Now having installed the plugins, we add some basic options and we're ready to go.

{
  resolve: `gatsby-transformer-remark`,
  options: {
    plugins: [
      // .. rest of the plugins
      'gatsby-remark-copy-linked-files',
      {
        resolve: `gatsby-remark-images`,
        options: {
          maxWidth: 900,
          quality: 90,
          withWebp: true,
        },
      },
    ],
  },
},

Not as polished as the gatsby-plugin-image, but it does the trick.

Fin

At the time of writing gatsby-plugin-image is still in beta. That said, I'm using it because life is too short to not live on the edge.

If you want to follow the official documentation, you can find it here

👋


Subscribe to the Newsletter

Subscribe to get my latest dev content by email.

I won't send you spam.
Unsubscribe at any time.