DEV Community

Cover image for Image Optimization Made Easy with Gatsby
Kyle Gill for Gatsby

Posted on • Originally published at kylegill.com

Image Optimization Made Easy with Gatsby

Shave off hours of work with a simple to use React component built for images. Optimizing images hardly sounds like an ideal afternoon of work, but in today’s world it’s become a necessary evil. This post looks at how it can be done much easier using gatsby-image.


For practical examples of methods used in this tutorial, I made a free playlist of Egghead videos that show how to use Gatsby Image.


Taking the time to crop down all your photos to different sizes, tinker with color depth and compression on your PNGs and JPEGs, write media queries for all the different sizes, or even add in lazy-loading of images can be time consuming and arduously dull. On the flip side, the results of image optimization can mean a much faster site, smaller request sizes on mobile devices, and overall happier users.

“…optimizing images can often yield some of the largest byte savings and performance improvements”
― Google PageSpeed docs

Enter gatsby-image! A React component designed for Gatsby.js as an easy way to add advanced image optimization to your site without having to jump through all the hoops.

Difference made by gatsby-image

The following gif shows the header loading without any optimizations from gatsby-image:

Header image loading without gatsby image

This next gif does use gatsby-image, notice how the whole image loads as a blurrier version first. The first gif loads pieces of the image from top to bottom and looks a lot clunkier:

Header image loading with gatsby image

A higher resolution image often loads somewhat like the first example, revealing an ugly background and expending precious milliseconds of load time.

Lazy-loading images or loading smaller-sized placeholders has become a standard way of improving the overall UX of a site. Facebook began using it as a technique to fix the jarring, jittery problem of a solid background suddenly transforming into an image. Medium also uses blurred images to preserve the layout of the site so that images don’t bump text or other DOM elements farther down the page as the images load.

What's the use case for gatsby-image?

While building out a demo restaurant site in Gatsby, I ran into some performance issues that almost all stemmed from some oversized images from Unsplash. I didn’t really need a 4000x6000 JPEG filling the hero section of my screen, but my <img> tags would argue otherwise. Running a Lighthouse Performance audit in the Chrome Developer Tools scored my site at a sluggish 32 out of 100. 🤭

Lighthouse score with a chart showing a score of 32

One of Gatsby’s biggest selling points is that the static sites it generates run “blazing-fast”. My project was a disappointing exception. By implementing gatsby-image I reasoned that my measly 32 could be improved significantly.

Implementation of gatsby-image

My project was based off of the gatsby-starter-default starter so I got started with gatsby-image by installing and verifying that I had the packages I’d need. To install the gatsby-image plugin with yarn I ran:

yarn add gatsby-image

Enter fullscreen mode Exit fullscreen mode

The gatsby-image plugin also requires that plugins gatsby-transformer-sharp and gatsby-plugin-sharp be installed and added to the config file. To do that I ran:

yarn add gatsby-transformer-sharp
yarn add gatsby-plugin-sharp
Enter fullscreen mode Exit fullscreen mode

And then opened my gatsby-config.js and included the following snippet:

plugins: [
  // additional plugins
  ...
  {
    resolve: `gatsby-source-filesystem`,
    options: {
      name: `data`,
      path: `${__dirname}/src/data/`
    }
  },
  `gatsby-transformer-sharp`,
  `gatsby-plugin-sharp`
]
Enter fullscreen mode Exit fullscreen mode

Right after, I added another block to allow GraphQL to access my images:

plugins: [
  // additional plugins
  ...
  {
    resolve: `gatsby-source-filesystem`,
    options: {
      name: `data`,
      path: `${__dirname}/src/data/`
    }
  },
  {
    resolve: `gatsby-source-filesystem`,
    options: {
      name: `img`,
      path: `${__dirname}/src/img/`
    }
  },
  `gatsby-transformer-sharp`,
  `gatsby-plugin-sharp`
]
Enter fullscreen mode Exit fullscreen mode

This snippet with resolve, options, name, and path allows me to query the /img directory inside of /src with GraphQL, which is how I’ll use it with the images in my filesystem. You can read more about how Gatsby uses GraphQL here.

With that set up, I went to refactor the code in my <Header /> component. My <Header /> is a child of a <TemplateWrapper /> component that is used on every page. Gatsby looks for GraphQL queries at build time inside of files in the /src/pages directory. Those queries load the props of their respective components with a data attribute containing the queried data. In order to use the gatsby-image component in my header, I had to write my GraphQL query in the <TemplateWrapper /> which is used to create the page and pass the data down as a prop.

To sum up that explanation in simpler steps:

  • <TemplateWrapper /> needed a query to get the necessary data for my optimized image
  • <TemplateWrapper /> would pass the data down to my <Header /> as a prop
  • <Header /> would plug that data into gatsby-image’s <Img /> component where the magic happens
  • To do this, I changed my <TemplateWrapper /> in my /src/layouts directory to include a small GraphQL query:
// imports
...
const TemplateWrapper = ({ data, children }) => {
  return (
    <div>
      <Helmet title="Contemporarium" />
      <Header headerImage={data.headerImage} />
      {children}
      <Footer />
    </div>
  );
}

export default TemplateWrapper;

export const pageQuery = graphql`
  query HeaderImageQuery {
    headerImage: imageSharp(id: { regex: "/header/" }) {
      sizes(maxWidth: 1240 ) {
        ...GatsbyImageSharpSizes
      }
    }
  }
`
Enter fullscreen mode Exit fullscreen mode

Note the headerImage prop being passed into the <Header /> component, which we’ll use inside its code.

Understanding the query:

The query is called HeaderImageQuery and uses an alias called headerImage on the imageSharp field to make it more readable. My image called header.jpg, is identified by the argument passed into imageSharp that looks for a file with header in its name via regular expressions.

Gatsby’s docs explain how queries should be written differently for 2 classifications of images that are explained here. Essentially any image will either be classified as either: (1) an exact size or (2) stretched to fill a container. Your query will look different depending on the nature of your image. Since my image will stretch across the header it’s of the second type, which means I query for the sizes field. I recommend reading the docs on this topic or looking at Gatsby’s examples for more help.

...GatsbyImageSharpSizes is a query fragment that includes several fields like sizes, originalName, aspectRatio, and several others so you don’t have to type them out yourself.

With the data in the sizes object getting passed down to my <Header />, I was ready to switch out my <img /> for the Gatsby equivalent! My header file went from this:

import React from 'react'

const Header = props => (
  <header className="header">
    <img
      title="Header image"
      alt="Greek food laid out on table"
      src="../img/header.jpg"
    />
  </header>
)

export default Header
Enter fullscreen mode Exit fullscreen mode

to this:

import React from "react";
import Img from "gatsby-image";

const Header = props => (
  <header className="header">
    <Img
      title="Header image"
      alt="Greek food laid out on table"
      sizes={props.headerImage.sizes}
    />
  </header>
);

export default Header
Enter fullscreen mode Exit fullscreen mode

Notice how little changed. I only had to add the import for gatsby-image, capitalize the tag, and change my src to sizes using the data in the sizes objects passed in from the GraphQL query I wrote, and now loading up my site with gatsby develop:

Successfully loading a hero image with gatsby image

By changing the fragment I used back in my GraphQL query under my <TemplateWrapper /> component I can change the loading style to something else like a traced SVG.

export const pageQuery = graphql`
  query HeaderImageQuery {
    headerImage: imageSharp(id: { regex: "/header/" }) {
      sizes(maxWidth: 1240 ) {
        ...GatsbyImageSharpSizes_tracedSVG
      }
    }
  }
`
Enter fullscreen mode Exit fullscreen mode

By just changing one line, I can change the image loading to look like this instead:

traced SVG loading with gatsby-image

Results

Now when I run a performance audit with Lighthouse I doubled my page’s score from 32 to 65! A few more optimization tweaks and the “blazing-fast” site promised by Gatsby is a reality.

Performance improvements from lighthouse showing a score of 65

All it takes is one plugin, a GraphQL query, swapping in a new component and you’ve got yourself a much improved user experience for your site.

Thanks for reading!…

If you thought this was interesting, leave a comment, follow for future updates, tweet at me with your thoughts, or check out the docs on the topic.

Top comments (0)