Skip to main content

Gatsby, Markdown, and Image Optimization

Front-end Development
Back-end Development

The Gatsby.js framework has some well known and loved utilities for image optimization, and also some new ones that make the user experience even better. 

The Basics ("what is sharp?")

Sharp is probably used on almost every Gatsby site, but most developers (including myself at one point) don't quite understand what pieces are doing what. We're told from the beginning to include a few plugins with the word "sharp" in it, here is what they do.

Taken directly from the Gatsby.js docs site:

Here is what these plugins look like when implemented in the configuration file. (note: other important, but not relevant parts of the config are omitted from this code snippet for brevity).

gatsby-config.yml

module.exports = {
  ...
  plugins: [
    'gatsby-transformer-sharp',
    'gatsby-plugin-sharp',
    {
      resolve: 'gatsby-source-filesystem',
      options: {
        path: `${__dirname}/content`,
        name: 'resources',
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/src/images`,
      },
    },
    ...
  ],
};

 

GraphQL

The next part is making sure you actually use the plugins you have defined in the config. It may seem odd to mention this, but simply querying for the most obvious image properties will result in doing the optimizing work without any of the benefits. 

For example, here is a common graphql query example for simply including image URL. Pay special attention to the banner property, which represents an image included in resource (in this case markdown source).

export const pageQuery = graphql`
  query ResourceIndexQuery {
    allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC }) {
      totalCount
      edges {
        node {
          id
          frontmatter {
            title
            date(formatString: "DD MMMM, YYYY")
            category
            banner {
              publicURL
            }
          }
          fields {
            slug
          }
          excerpt
        }
      }
    }
  }
`;

In the above code, we reasonably assume that to display the image on our site, we should extract the publicURL from the field we have defined for our image.  This displays the image, but does so at the original resolution. 

To leverage the Sharp plugins we need to extract the image URL differently.  See the changes below for the banner field.

export const pageQuery = graphql`
  query ResourceIndexQuery {
    allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC }) {
      totalCount
      edges {
        node {
          id
          frontmatter {
            title
            date(formatString: "DD MMMM, YYYY")
            category
            banner {
              childImageSharp {
                fluid(maxHeight: 500) {
                  ...GatsbyImageSharpFluid
                }
              }
            }
          }
          fields {
            slug
          }
          excerpt
        }
      }
    }
  }
`;

We are now replacing publicURL with

childImageSharp {
  fluid(maxHeight: 500) {
    ...GatsbyImageSharpFluid
  }
}

Fluid indicates that Gatsby.js will make a number of resolutions that can be used for various client window sizes.  (fixed is also an option)

We use the "spread" operator to make all the properties accessible of GatsbyImageSharpFluid which is required to implement gatsby-image below.

React Component

With sharp included in the graphql, we can reference the needed URL with the src property. (This would also require replacing ...GatsbyImageSharpFluid with simply src)

<img src={data.markdownRemark.frontmatter.banner.childImageSharp.fluid.src} />

Progressive image loading

But we can easily make a very cool enhancement to our site by including gatsby-image. This plugin does NOT need to be included in the gatsby-config.js file, but it does need to be imported for your component.

import Img from 'gatsby-image';

Then, instead of using a traditional img tag, use the imported component and pass it the fluid property (no further specification needed beyond that).

<Img
  fluid={node.frontmatter.banner.childImageSharp.fluid}
  title="Banner Image"
/>

You can read more about this plugin here. But the big win is lazy / progressive image loading (like Facebook).  A low-resolution version of the image is shown first and then updated with a full-resolution version later. It is a subtle change that really improves the user experience.