Working with images in Gatsby

📅 Sunday, February 3rd 2019

5 minutes read

athens.jpg

Working with images isn't very pleasant. We would rather just code.

SEO, accessibility, and image handling are serious aspects of web development that we developers sometimes overlook. Using unprocessed images leads to increased load times, abrupt shifts in content positioning and overall bad UX. Imagine if you started reading this blog post, only to have the text being pushed 500px down after the above image loaded!

Originally that was how my website was handing the images. My layout wasn't reliant on them, and SVG icons did the trick nicely.

Gradually, I started adding images in my posts and introduced this pink guy in the Hero section.

This raised a couple of issues.

  1. Having to assure that all the images have the proper dimensions for each device (srcSet) and are properly optimized. Downloading the full 4mb .png image in cellular data is no fun.
  2. Deal with the aforementioned annoying shift in content when the images finally load.

Now addressing the first point isn't very hard but it can be a tedious task. We have to generate 2-3 versions for the srcSet, account for Retina screens, compress them, as well as generate a WebP version for immense savings.

Still, we're bound to request all the images during the initial load time. No matter the savings, we're still having a certain performance hit for assets that are not critical.

Thankfully, Gatsby offers some nice utilities to ease these pains and let us focus on other things.

Images in markdown files

Before going into the main point. I would like to share one of my most favourite Gatsby plugins. I like to bundle the post's assets (not limited to images) with the actual document. For this case we can use gatsby-remark-copy-linked-files. Can reference the images relatively like this

![athens.jpg](athens.jpg);

and Gatsby will make sure that they will be moved to the public folder during build-time. Of course, the final HTML output will have the correct path reference.

Ok, now let's move on to the image rendering - we're going to add gatsby-remark-images.

{
  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,
        },
      },
    ],
  },
},

This is the actual configuration of my blog. The post body has a max-width of 900px, so we can safely limit the images to this width. Then, I state that having WebP images are desired. The plugin's API is rather small and opinionated but that's acceptable for what we need to do.

The most important point is that it loads a placeholder container at the size of the image, with a blurred thumbnail of it. When the image is visible on the viewport, it will be then fully loaded.

Images in Page components

In the page components, we have better control of the generated images.

First of all, we have to let know where our images are located, so they will be available for queries.

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

Then, we can select the images we like and apply the appropriate Sharp transformations when we do our GraphQL query.

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

  1. Images with fixed width
  2. Images that stretch across their fluid parent container

The pink-guy-that-works image in the Hero section is been brought to life using this query:

query {
  heroImg: file(relativePath: { eq: "cover.png" }) {
    childImageSharp {
      fluid(quality: 100) {
        ...GatsbyImageSharpFluid_withWebp
      }
    }
  }
  // more ...
}

GatsbyImageSharpFluid_withWebp means that we want a fluid image, in WebP format (with fallbacks of course), and without any quality loss.

Here are some more options

  • GatsbyImageSharpFixed
  • GatsbyImageSharpFixed_noBase64
  • GatsbyImageSharpFixed_tracedSVG
  • GatsbyImageSharpFixed_withWebp
  • GatsbyImageSharpFixed_withWebp_noBase64
  • GatsbyImageSharpFixed_withWebp_tracedSVG
  • GatsbyImageSharpFluid
  • GatsbyImageSharpFluid_noBase64
  • GatsbyImageSharpFluid_tracedSVG
  • GatsbyImageSharpFluid_withWebp
  • GatsbyImageSharpFluid_withWebp_noBase64
  • GatsbyImageSharpFluid_withWebp_tracedSVG

For a more detailed walkthrough, you can consult the official Gatsby docs

Referencing images

An alternative when fetching images in batches is to reference them in a file that Graphql parses.

See the following example, where we store the gallery images in a sibling folder called 'images/gallery'.

// src/content/home/homepage.json
{
  "title": "Home",
  "gallery": [{
    "title": "Lorem ipsum dolor sit amet",
    "copy": "consectetur adipiscing elit.",
    "image": "../images/gallery/react-context.jpg"
  },
  {
    "title": "Lorem ipsum dolor sit amet",
    "copy": "consectetur adipiscing elit.",
    "image": "../images/gallery/page-transitions.jpg"
  },
  {
    "title": "Lorem ipsum dolor sit amet",
    "copy": "consectetur adipiscing elit.",
    "image": "../images/gallery/intersection-observer.jpg"
  }]
}
// src/gatsby-config.js
{
  resolve: `gatsby-source-filesystem`,
  options: {
    name: `content`,
    path: `${__dirname}/content`,
  },
},

Then have them apply the desired transformation, without having to select them one by one.

export const query = graphql`
  query HomepageQuery {
    homeJson {
      title
      gallery {
        title
        copy
        image {
          childImageSharp {
            fluid(maxHeight: 300, quality: 90) {
              ...GatsbyImageSharpFluid_withWebp
            }
          }
        }
      }
    }
  }
`;

Images in non-Page components

Non-page components, meaning components that are not used for routing cannot do Graphql queries the same way. Instead, we have to use Static query

Here's how the above example would play out in a non-page component

render(){
  return <div className='some-class'>
    <StaticQuery
      query={graphql`
        heroImg: file(relativePath: { eq: "cover.png" }) {
              childImageSharp {
                fluid(quality: 100) {
                  ...GatsbyImageSharpFluid_withWebp
                }
              }
            }
      `}
      render={({heroImg})=> (
        <Img fluid={heroImg.childImageSharp.fluid} alt='Working man'/>
      )}
    />
    <div>.
      .. More content
    </div>
  </div>
}

Now you can either use StaticQueries or pass down the image's metadata as props. It totally depends on you and your use case.

Notice that the Img element used above comes from the gatsby-image package. It's a great component that consumes the image object and renders the appropriate image tags. You can do it yourself, but in most, if not all cases, it's enough.

#winning winning.jpg

Thanks for reading!  ❤️
Next up: Things I don't know as of Feb '19
Previous: First months in Brazilian Jiu-Jitsu
Due to privacy concerns, I've decided to remove Disqus.
Until I settle for another commenting solution, you can share your thoughts with me via e-mail
Back to Posts