How to Automatically Optimize Responsive Images in Gatsby

Image optimization — at least in my experience — has always been a major pain when building speedy websites. Balancing image quality and bandwidth efficiency is a tough act without the right tools. Photo editing tools such as Photoshop are great for retouching, cropping and resizing bitmap images. Unfortunately, they are not that good at creating 100% optimized images for the web.

Luckily, we have extension packages for build tools that can optimize images for us quickly:

Unfortunately, image optimization alone is not enough. You need to make sure that the entire website is responsive and looks great at all screen sizes. This can easily be done through CSS, but here lies the problem:

Should you optimize your image for large screens or small screens?

If the majority of your audience is using mobile devices to access your site, then the logical choice is to optimize images for small screens. However, it’s likely that a significant source of revenue is coming from visitors with large screens over 17″. You definitely wouldn’t want to neglect them.

Luckily, we have technology that allows us to deliver optimized responsive images for different screen sizes. This means we need to generate multiple optimized images with different resolutions fit for specific screen sizes or responsive breakpoints.

For WordPress site owners, this kind of image optimization requires the use of a plugin and a third-party service. The creation of these responsive images cannot be done on the hosting server without significantly slowing down the site for users, hence the need for a third-party service.

If you are using Gatsby to run your website, then you are in luck. This feature is built-in and already configured for you to optimize your responsive images. You just need to drop in some images and write a bit of code to link up your responsive images with your web page. When you run the gatsby build command, the images are optimized for you. This saves you from requiring a third-party service to perform the optimization for you. It’s simply done on your deployment machine.

In the subsequent sections, we are going to learn:

  • How image optimization works in Gatsby
  • How to optimize images on a web page
  • How to optimize images in a Markdown post

Prerequisites

Before we start, I would like to note that this tutorial is for developers who are just starting with Gatsby and would like to learn specifically about how to handle images. I am going to assume you already have a good foundation in the following topics:

This tutorial doesn’t cover beginner concepts for Gatsby — we have a getting started with Gatsby guide here. With that out of the way, head over to the next section to set up our demo project. You can view the completed source project here.

Demo Project Setup

Assuming you already have a recent version of Node.js installed on your system, let’s quickly set up a Gatsby starter project:

npm install -g gatsby-cli
gatsby new gatsby-image-demo
cd new gatsby-image-demo
npm start

This starter project includes the necessary dependencies and configuration required for creating and rendering responsive optimized images. If you used a different starter project or you preferred starting from a completely blank project, this is what you will need to do:

npm install gatsby-image gatsby-transformer-sharp gatsby-plugin-sharp gatsby-source-filesystem

Next, you’ll need to configure the dependencies as follows in gatsby-config.js:

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

If you open http://localhost:8000/, you should have the Gatsby Default Starter page with an optimized PNG image of an astronaut. Feel free to look through the project code to see how the image was loaded and rendered.

It looks pretty complicated, right? Well, in the next section we’ll look at what that’s all about.

Image Optimization in Gatsby Explained

Rendering an optimized responsive image on a web page is done using Gatsby Image, a React component. It looks like this:

import Image from 'gatsby-image';

<!--  Fixed Image Example -->
<Image fixed={fixed} />

<!--  Fluid Image Example -->
<Image fluid={fluid} />

As seen in the above code samples, there are two types of images that the gatsby-image component is designed to handle:

  • Fixed: image with fixed width and height
  • Fluid: image with maximum width, and possibly height

Fixed is useful if you want to display retina images. Note that scroll bars will appear if the browser window is resized smaller than the image width. For fluid, the image will resize automatically based on the browser window size. Smaller or larger images will be swapped automatically to fit within a set viewport.

Now that we have talked about rendering, how does one provide image data to a gatsby-image component?

We use GraphQL to load an image for use on a web page. This query language allows us to access images from the local filesystem, a WordPress site or a custom API. You will need a special plugin to access a particular location type:

GraphQL not only fetches assets but is also capable of processing them before returning them to the calling function. In the case of image optimization, we are dealing with the following plugins:

The Gatsby Plugin Sharp is a low-level helper plugin that does the actual work of reducing image size with zero or minimal loss of image quality. It uses the Sharp image processing library to perform this task. For JPEGs, it generates progressive images with a default quality level of 50. For PNGs, it uses the pngquant library with a quality setting of 50-75.

The Gatsby Transformer Sharp plugin is responsible for creating responsive images. In other words, it performs resizing and cropping functions to generate different resolutions of an image for optimum display on mobile, tablet and large-screen devices.

In the next section, we’ll look at the practical usage of the above technologies.

Optimize Images on a Web Page

Let’s first start by dropping some images in the src/images folder:

01-source-images

Feel free to use any image on your hard drive or from the internet. If you plan on using high resolution DSLR photos, I’d recommend you at least bring the size down to 700kb and below. Using large images will unnecessarily prolong the build optimization process, and will balloon the size of your project repository.

Next, let’s figure out the GraphQL queries that we’ll use to query our responsive optimized images. Open http://localhost:8000/___graphql in your browser to launch the GraphQL Explorer and Query interface. On the Explorer panel, take note of all the nodes we have available to us. In our case, we are only interested in the file and childImageSharp nodes. Below is a simple query that I have constructed. The Explorer panel will list all the parameters and nodes you can use to define your query:

02-graphql-explorer

Now that we have defined a GraphQL query, let’s create a new page, say grado.js. In the following code example, we are going to render both fixed and fluid images. However, for the query part, we’ll use GatsbyImageSharpFluid and GatsbyImageSharpFluid query fragments instead of listing all the required child nodes (i.e. src, sizes, srcSet etc). Do note that query fragments are not yet supported in GraphQL Query Explorer.

import React from "react"
import Image from 'gatsby-image';
import { graphql } from "gatsby"

import Layout from "../components/layout"
import SEO from "../components/seo"

const GradoPage = ({data}) => (
  <Layout>
    <SEO title="Grado" />
    <h1>Grado Headphones ShowCase</h1>
    <h3>Fluid</h3>
    <Image fluid={data.gradoFluidImage.childImageSharp.fluid} />
    <br/>
    <h3>Fixed</h3>
    <Image fixed={data.gradoFixedImage.childImageSharp.fixed} />  
    <p>Grado Rs2e</p>
  </Layout>
)

export default GradoPage

export const pageQuery = graphql`
  query {
    gradoFluidImage: file(relativePath: { eq: "grado-rs2e.jpg" }) {
      childImageSharp {
        fluid(maxWidth: 1000) {
          ...GatsbyImageSharpFluid
        }
      }
    }
    gradoFixedImage: file(relativePath: { eq: "grado-rs2e.jpg" }) {
      childImageSharp {
        fixed(width: 600, height: 401) {
          ...GatsbyImageSharpFixed
        }
      }
    }
  }
`

Assuming Gatsby is still running, navigate to localhost:8000/grado:

03-fixed-vs-fluid

The example above will show you the visual difference between fluid and fixed images. The fluid image will always fit within the container width, while the fixed image will remain static regardless of the viewport size.

In the next code example, we’ll look at how we can list multiple fluid images on the same page:

const GradoPage = ({data}) => (
  <Layout>
    <SEO title="Grado" />
    <h1>Grado Headphones ShowCase</h1>
    <h3>Grado</h3>
    <Image fluid={data.grado.childImageSharp.fluid} />
    <br/>
    <h3>Grado Boxed</h3>
    <Image fluid={data.gradoBox.childImageSharp.fluid} />
    <br/>
    <h3>Grado Mounted</h3>
    <Image fluid={data.gradoMounted.childImageSharp.fluid} />
  </Layout>
)

export default GradoPage

export const pageQuery = graphql`
  query {
    grado: file(relativePath: { eq: "grado-rs2e.jpg" }) {
      childImageSharp {
        fluid(maxWidth: 1000) {
          ...GatsbyImageSharpFluid
        }
      }
    }

    gradoBox: file(relativePath: { eq: "grado-rs2e-box.jpg" }) {
      childImageSharp {
        fluid(maxWidth: 1000) {
          ...GatsbyImageSharpFluid
        }
      }
    }

    gradoMounted: file(relativePath: { eq: "grado-rs2e-mounted.jpg" }) {
      childImageSharp {
        fluid(maxWidth: 1000) {
          ...GatsbyImageSharpFluid
        }
      }
    }
  }
`

The /grado page should refresh automatically. You should see all the images appear on the page. If you try downloading one of the images right from the browser, you’ll see that size has been reduced. In my case, if I shrink the browser to the smallest width, the ‘Grado Box’ image is reduced to 19.5 KB. When I maximize the browser on my 17″ 1920×1080 screen, the image size is increased to 60.1 KB which still looks pretty sharp. These are pretty awesome numbers considering the source image I placed in the images folder weighs 618KB at a resolution of 2500x1800px.

You may have noticed that the query is looking redundant. We can simplify by creating our own query fragment as follows:

export const fluidImage = graphql`
  fragment fluidImage on File {
    childImageSharp {
      fluid(maxWidth: 1000) {
        ...GatsbyImageSharpFluid
      }
    }
  }
`;

export const pageQuery = graphql`
  query {
    grado: file(relativePath: { eq: "grado-rs2e.jpg" }) {
       ...fluidImage
    }

    gradoBox: file(relativePath: { eq: "grado-rs2e-box.jpg" }) {
       ...fluidImage
    }

    gradoMounted: file(relativePath: { eq: "grado-rs2e-mounted.jpg" }) {
       ...fluidImage
    }
  }
`

Optimize Images in Markdown Posts and Pages

There are two ways of optimizing images in Markdown posts and pages:

1. Featured Images

Featured images are usually placed within the metadata section. You just need to specify a field called featuredImage, like this:

---
title: First Post
featuredImage: ./first-post-image.png
---

Place content here

Next, you need to process the featuredImage in your Markdown template file, like this:

//src/templates/blog-post.js
---
export const query = graphql`
  query PostQuery($slug: String!) {
    markdownRemark(fields: { slug: { eq: $slug } }) {
      html
      frontmatter {
        title
        featuredImage {
          childImageSharp {
            fluid(maxWidth: 800) {
              ...GatsbyImageSharpFluid
            }
          }
        }
      }
    }
  }
`

You will also need to import the gatsby-image package in order to render your optimized responsive image:

//src/templates/blog-post.js

import Img from "gatsby-image"

export default({data}) {
  let post = data.markdownRemark
    let featuredImgFluid = post.frontmatter.featuredImage.childImageSharp.fluid
    return(
        <Layout>
          <div>
            <h1>{post.frontmatter.title}</h1>
            <Img fluid={featuredImgFluid} />
            <div dangerouslySetInnerHTML={{ __html: post.html }} />
          </div>
        </Layout>
    )
}

That’s it. All your Markdown posts will have the featuredImage field optimized for responsive screens.

2. Inline Images

For inline images used in Markdown posts and pages, all you have to do is install the following plugins:

npm install gatsby-remark-images

You will also need gatsby-plugin-sharp and gatsby-source-filesystem installed as well. Next, you’ll need to configure gatsby-remark-images in gatsby-config.js as follows:

module.exports = {
  plugins: [
    `gatsby-plugin-sharp`,
    {
      resolve: `gatsby-plugin-mdx`,
      options: {
        gatsbyRemarkPlugins: [
          {
            resolve: `gatsby-remark-images`,
            options: {
              maxWidth: 1200,
            },
          },
        ],
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/src/pages`,
      },
    },
  ],
}

In your Markdown posts and images, you can use the default syntax to render images. The optimization will be done for you automatically:

![Post image](./my-first-post-image.png)

Summary

I hope you now understand how to optimize images responsively for Gatsby sites. There are a few scenarios we haven’t covered here — you can read more about them on the relevant Gatsby package pages: