Learning to Work With Images in Gatsby Sites

Note: This is part 3 of five-parts adding functionality to Gatsby site series. This learning post is still in active development and updated regularly.

In a previous posts in this series, adding syntax highlighting with PrismJs and Styling Gatsby Site with Typography & Sass Plugins were discussed.

Images are integral part of any website however image optimization is a daunting task. All images has to be prepared for different device screen sizes (srcSet) and optimized. Websites using not-optimized images take longer time to download and offer sub-optimal user experience (eg. content-shift). One of the main attractions of Gatsbyjs is its image optimization & lazy-loading features with gatsby-image component thus offering blazing fast performance.

Learning series
Part 1: Adding Syntax highlighter with Prismjs
Part 2: Styling Gatsby Site with Typography & Sass Plugins
Part 3: Learning to Work With Images in Gatsby Sites (this post)
Part 4: Adding Tags to Posts in Gatsby Site
Part 5: Adding Pagination to Gatsby Site

In this part 3 of five-parts learning-note post series, we will explore how images are added to Gatsby sites including adding images in markdown files often used in blog posts.

Adding Images in Gatsby sites

In the previous An Overview of Gatsby Plugins & GraphQL post, images were used with <img> tag and attributes (eg., src) in Gatsby page components (eg. index.js, about.js, etc.). In Gatsby sites, images are added with JavaScript import statement into the component and using Gatsby Image plugin.

Using JavaScript Import

The most basic method of adding images in Gatsby sites is through JavaScript import into the page component. The imported images (assets) can be assessed in the code by referencing file path with <img> tag and src or href attributes. A very basic example from the Gatsby Docs:

// header.js
import React from "react"
import logo from "./logo.png"

function Header() {
  // Import result is the URL of your image
  return <img src={logo} alt="Logo" />
}
export default Header

n the example above, an logo.png image is imported from a file (line 3) into the header.js component and referenced with <img> tag in the body of the Header function (line 7).

Using gatsby-image plugin

The gatsby-image component is a “React component specially designed to work seamlessly with Gatsby’s GraphQL queries“. With GraphQL and Sharp powered gatsby-image plugin, images are optimized as described in the Gatsby Docs:

  • Importing gatsby-image in page component and using it in place of the built-in img
  • Writing a GraphQL query using one of the included GraphQL “fragments” which specify the fields needed by gatsby-image.

The GraphQL query “creates multiple thumbnails with optimized JPEG and PNG compression. The gatsby-image component automatically sets up the “blur-up” effect as well as lazy loading of images further down the screen.

Note: Handing of images in Gatsby sites with example use cases is discussed in a separate post – Deep Diving Into Working with Images in Gatsby Sites.

Getting Started with Gatsby-Image

Step 1: Plugin Installation

First step is to install the gatsby-image plugin and its dependencies (gatsby-transformer-sharp and gatsby-plugin-sharp).

#! install plugins
yarn add gatsby-image

#! install dependency plugins
yarn add gatsby-transformer-sharp gatsby-plugin-sharp

If not install before, install gatsby-source-filesystem plugin as well, which allows the plugins to query files with GraphQL.

  • gatsby-transformer-sharp plugin: allows to create multiples images of the right sizes and resolutions with a query.
  • gatsby-plugin-sharp plugin: provides connection between the Sharp and Gatsby plugin.
  • gatsby-image plugin: a React component that optimizes responsive images using GraphQL and Gatsby’s data layer
Step 2: Create Image folder and add working Images

For this post, the images are placed in a images sub-folder in data folder at the project root (eg., /data/images). The images folder includes a few images downloaded from unsplash with large dimension.

#! partial project structure
.
├── node_modules
├── data
│   └── images
├── src
├── gatsby-browser.js
├── gatsby-config.js
├── gatsby-node.js
├──
Step 3: Gatsby-image Plugin Configuration

Add the plugins in gatsby-config.js file and configure as shown below:

// gatsby-config.js
module.exports = {
  plugins: [
    ...
    {
    resolve: 'gatsby-source-filesystem',
    options: {
      name: `posts`,
      path: `${__dirname}/src/posts`,
      },
    },
    {
     resolve: 'gatsby-source-filesystem',
     options: {
       name: `data`,
       path: `${__dirname}/data`,
     },
    },
    `gatsby-transformer-sharp`,
    `gatsby-plugin-sharp`, 
    ...
  ],
}

It is important to add correct path to the image directory so that the gatsby-transformer-filesystem plugin has access to images in the image directory.

Note: The gatsby-image plugin, once installed in project directory, does not need to be included in gatsby-config.js file.

Step 4: Image query with GraphQL

The gatsby-transformer-filesystem plugin has access images in the image folder and creates file nodes from the images thus making them available through GraphQL queries.

Start the development server and navigate to localhost:8000/___graphql and test whether GraphQL image query works and access to different GraphQL queries.

Figure: Screenshot of ..

The above screenshot shows that allImageSharp and imageSharp nodes are available.

We also have access to childImageShap through allFile or file nodes and query a specific image file.

Types of Image Optimization

The Gatsby image API describes two types of image optimization options available, fixed and fluid.

  • Fixed: Creates different resolutions of images (eg., 1x, 1.5x, 2x pixel densities) with fixed width and  height parameters suitable for different screen devices.
  • Fluid: Creates different flexible (responsive) size images that stretch to fill its container width providing optimized images for every device and screen resolution.
  • Fixed vs Fluid images: If exact image size is desired for different devices/resolutions then the fixed image is appropriate. On the other hand, if the width and/or height needs to vary depending on the device and screen resolutions, the fluid image is ideal.

The GraphQL  query image data from fixed or fluid objects are passed as fixed or fluid props to the <Img /> component as <Img fixed={fixed} /> or <Img fluid={fluid} /> respectively. Various parameters options for fixed image query or fluid image query could also be specified in the image query.

Query Fragments

The Gatsby Image API supports reusable query fragments through its gatsby-transformer-sharp plugins. These fragments extract all the available fixed or fluid query parameters (eg., base64, max-width, sizes, src etc.,) in one line.

Use Case Examples

In the following sections, we will add a few GraphQL image queries to a page, non-page component and markdown posts.

  • Page Components: Page components are path-based and are used for routing. The GraphQL retrieves data from these components using page query.
  • Non-page Components: Components that are part of other component (eg. header, footer, layout components) and are not used for routing. The GraphQL queries these components with static query.

Images in Page Component

For this project, we will create a new images.js page component, similar to about.js page. Then images.js is added to the <Header /> component so that it is included into the top page navigation.

Step 1: Importing <Image /> Component & GraphQL

Import the <Image /> component and graphql package from gatsby to the images.js component.

// src/pages/images.js
import React from "react"
import { graphql } from "gatsby"
import Img from "gatsby-image"
import Layout from "../components/layout"

const MyImages = (props) => (
  <Layout>
    <div>
    <h1 className="page-title">Images with Page Query</h1>
      <Img fluid={props.data.myNature.childImageSharp.fluid} alt="" />
      <p>The above image is added with single GraphQL image query. </p>
      <Img fluid={props.data.myYoga.childImageSharp.fluid} alt="" />
      <p>The image below is added using multiple GraphQL image query. </p>
    </div>
  </Layout>
)

export default MyImages;

The GraphQL image query data props used above (lines: 11, 13) is explained in steps described below.

Step 2: Adding GraphQL Image Query to Page Component

First lets start querying single image from our images folder.

// graphql query
export const pageQuery = graphql`
  query {
    myNature: file(relativePath: { eq: "nature.jpg" }) {
      childImageSharp {
        fluid(maxWidth: 1000, maxHeight: 350) {
          ...GatsbyImageSharpFluid
       }
     }
  }
 myYoga: file(relativePath: { eq: "yoga.jpg" }) {
   childImageSharp {
    fluid(maxWidth: 1000, maxHeight: 350) {
       ...GatsbyImageSharpFluid
     }
  }
 }
}`;

If there are several images like in an image gallery, and same formatting is desired, the Gatsby Doc suggests using fragments to standardize formatting. The above code can be refactored using fragments as shown below:

// graphql query
export const fluidImage = graphql`
  fragment fluidImage on File {
    childImageSharp {
      fluid(maxWidth: 1000, maxHeight: 350) {
        aspectRatio
        ...GatsbyImageSharpFluid
      }
    }
  }
`;

export const pageQuery = graphql`
  query {
    myNature: file(relativePath: { eq: "nature.jpg" }) {
      ...fluidImage
    }
    myYoga: file(relativePath: { eq: "yoga.jpg" }) {
      ...fluidImage
    }
  }
`;

In the example above, first GraphQL image fragment named fluidImage (lines: 21-22 ) was defined (lines: 21-30) and was referenced in the respective image query (lines: 35, 38) above.

The relative path is not relative the images.js page component but relative to the image folder specificied with the gatsby-source-filesystem plugin in gatsby-config.js file.

Step 3: Add Graphql Query Data (props) to Image Component

An example of the GraphlQL queried data (above) can be assessed with props and added to <Image /> component as shown below.

// src/pages/images.js
<div>
   <h1 className="page-title">Images with Page Query</h1>
     <Img fluid={props.data.myNature.childImageSharp.fluid} alt="" />
     <p>The above image is added with single GraphQL image query. </p>
     <Img fluid={props.data.myYoga.childImageSharp.fluid} alt="" />
     <p>The image below is added using multiple GraphQL image query. </p>
</div>

After fresh re-starting the development server and view the inserted images in a browser at localhost:8000 (shown below).

Figure: Screenshot of images added to page component with single graphql query (top) and multiple graphql query (bottom). Images source: Unsplash.

Images in Non-Page Component

Unlike in the Page components, the GraphQL queries in non-page components are done using Static query which does not accept variables.

Step 1: Create a Simple Page Component

For this project, we will create a new photos.js component (shown below), similar to about.js page and add to the <Header /> component to include into the top page navigation.

//src/pages/photos.js
import React from "react"
import { useStaticQuery, graphql } from "gatsby"
import Img from "gatsby-image/withIEPolyfill"
import Layout from "../components/layout"

const MyPhotos = () => {
  return (
   <Layout>
     <div>
      <h1 className="page-title">Images with Static Queries</h1>
     </div>
    </Layout>
  )
export default myPhotos;

First step is to Import graphql & useStaticQuery packages from gatsby to the photos.js component (line 3). Next, the <Img /> component needs to be imported from gatsby-image package (line 4) before it can be used.

Step 2: GraphQL single Image Query

View the graphql at localhost:8000/___graphql and explore image query in-browser IDE. Refactor the photos.js component based on graphql image query as shown below:

// src/pages/photos.js
import React from "react"
import { useStaticQuery, graphql } from "gatsby"
import Img from "gatsby-image/withIEPolyfill"
import Layout from "../components/layout"

const MyPhotos = () => {
  const data = useStaticQuery(graphql`
     query Image {
      image: file(relativePath: { eq: "yoga.jpg" }) {
        id
        childImageSharp {
          fixed(width: 150, height: 150) {
            ...GatsbyImageSharpFixed
         }
          fluid(maxWidth: 1200, maxHeight: 600) {
            aspectRatio
            ...GatsbyImageSharpFluid
         }
     }
    }
`)
return (
  <Layout>
   <div>
    <h1 className="page-title">Images with Static Querries</h1>
     <p>Image added with static (non-page) querries.</p>
     <Img fixed ={data.image.childImageSharp.fixed}
     alt="A Lovely Cat" />
     <p>The above image is a fixed image with 150px x 150px dimensions. </p>
     <Img fluid ={data.image.childImageSharp.fluid} alt="A Lovely cat" />
     <p>The above image is a responsive fluid image, which covers
        content width. </p>
    </div>
  </Layout>
  )
}

export default MyPhotos;

In the example above, graphql and useStaticQuery packages are imported from Gatsby (line 3) and <Img /> component from gatsby-image package (line 4).

In the MyPhotos function body, a data object is setup which uses useStaticQuery(graphql) (line 8). Then a query called image and name it as image is setup to query an image named yoga.jpg in node in its relativePath (line 10) and extracted ChildImageSharp (a child node) with both fixed and fluid image types using their corresponding image fragments GatsbyImageSharpFixed (lines: 13-15) and GatsbyImageSharpFluid (lines: 16-19) respectively.

Finally, graphql query data for the fixed and fluid image were passed to the <Img /> component at lines 28-28 (fixed) and line 31 (fluid) respectively.

Step 3: View in a browser

After fresh re-starting the development server and view the inserted images in a browser at localhost:8000 (shown below).

Figure: Screenshot showing images added to page with graphql static query using fixed & flexible fragment (A) and graphql multiple image query (B). Images source: Unsplash.
Step 5: GraphQL Multiple Image Query

To query bunch of images from an image directory (eg. gallery) and append below the graphql query (insert between lines: 20-21 above, step 3)

//gallery images query
# multiple graphql image query
   images: allFile(filter: {relativeDirectory: {eq: "gallery"} }) {
     nodes {
       id
       childImageSharp {
       fixed(width: 178, height: 178) {
          ...GatsbyImageSharpFixed
       }
     }
    }
   }

Likewise, append the following code in Layout section (above) after line 35 in photos.js in step 3.

// insert
  <div className="gallery">
   <h3>Adding Gallery Images</h3>
    <p> Adding Multiple images (gallery) GraphQL queries in a
        page.</p>
      {data.images.nodes.map(image => (
      <Img key={image.id} fixed={image.childImageSharp.fixed}
      objectPosition="50% 50%"
      alt="This is my test gallery."
      />
       )) }
   </div>

Loop over the array of image nodes returned from the query as data.images.nodes.map and then pass each node‘s fixed property as image.childImageSharp.fixed as its value (lines: 6-11). This renders the imageSharp node with multiple images in Gallery.

Step 6: View in a browser

After fresh re-starting the development server and the inserted gallery images show in a browser at localhost:8000 (shown in the screenshot above in step 4, right B).

Images in Markdown Files

Gatsby files are primarily based on Markdown, it is essential to understand how Gatsby handles images in Markdown files. Most blog posts contain featured image and images in the body to enhance the content.

Images in Markdown Files are added either in Frontmatter and/or inline images in the body of the Markdown file.

1. Adding Featured Images in Blog Posts

The featured images or cover images features in websites with blog posts are often added to enhance the content and user experience. Such images are often added at the top section of a post often just below or above the post title. In Gatsby sites where posts are written with Markdown editor, featured images are often added with Frontmatter metadata and transforming it with gatsby-plugin-sharp in a GraphQL query.

Step 1: Create Markdown Post File

In the example below, a featuredImage field is defined with a butterfly.jpg image in the frontmatter metadata of the index.md markdown file (line 5) in the image-in-markdown-post folder. For convenience, the butterfly.jpg image is placed in the same folder.

<!---(contents/posts/image-in-markdown-post/index.md)--->
---
title: Images in Markdown Posts
date: "2019-09-03"
featuredImage: ./butterfly.jpg
---
The above image is used as a **featured image**. Lorem Ipsum is simply dummy
text of the printing and typesetting industry.

### Inline Images in Markdown  File

The following image is used as an **inline image**. Contrary to popular
belief, Lorem Ipsum is not simply random text.

![A Natural Beauty](./nature.jpg)

Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia,
looked up one of the more obscure Latin words, consectetur, from a Lorem
Ipsum passage, and going through the cites of the word in classical
literature, discovered the un-doubtable source.

In the makdown body an inline image named .nature.jpg is also added (line 15) which will be discussed in the Adding Inline Images to Markdown Blog Posts section (below).

Step 2: Add Images to Markdown Post Folder

It’s important that the images used in the markdown pages should include its relative path. In the example used, it is assumed that the image is in the same folder with posts for convenience. If it is desired to use images from images folder it can be referenced with relative file path.

Tips: The relative paths can be tricky some time, revisit file path especially your image is not rendered. As a reminder, ./ refers in the same folder, ../ out of 1 folders and starts there; ../../ refers out of 2 folders and start there and so on.. .

Step 3: Query for Featured Image in GraphQL

The frontmatter metadata exposes the image to GraphlQL through its filepath and the image data can be accessed at File node in GraphQL. The image data are queried out of its childImageSharp field and added to posts template (eg., Index.js and/or, Blog-post.js) file.

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

Within the ImageChildSharp field, images can be queried as fixed or fluid. In the example above a fluid query is used by referencing GatsbyImageSharpFluid fragment.

Step 4: Add GraphQL Image Query to Blog Template

Because I would like to add featured image in the single blog post (Blog-post.js) only and not in the blog listing (Index.js) component. The image query data from previous section are added to Blog-post.js component (lines: 32-38).

// src/templates/blog-post.js
import React from "react"
import { graphql } from "gatsby"
import Img from 'gatsby-image'
import Layout from "../components/layout"

export default ({ data }) => {
  const post = data.markdownRemark

    return (
      <Layout>
       <div>
        <h1 className="single-entry-title">{post.frontmatter.title}</h1>
       <div className="align-wide">
       {post.frontmatter.featuredImage && (
        <Img fluid={post.frontmatter.featuredImage.childImageSharp.fluid} />
       )}
       </div>
        <div dangerouslySetInnerHTML={{ __html: post.html }} />
       </div>
    </Layout>
)
}

//GraphQL query
export const query = graphql`
  query($slug: String!) {
    markdownRemark(fields: { slug: { eq: $slug } }) {
       html
       frontmatter {
         title
         featuredImage {
          childImageSharp{
              fluid(maxWidth: 1600, maxHeight: 900) {
                  ...GatsbyImageSharpFluid
              }
          }
          }
        }
    }
  }
`

To be able to pass the GraphQL image query data, gatsby-image package is imported to the post template (line 4) and passed into an <Img /> component just below the post title (line 16).

Conditional Checks for Images

It is import to add conditional statements to check whether there is featured image, if so then display <div> and images. This helps to resolve the post even if there is no featured image in post Frontmatter.

//src/templates/blog-posts.js (only relevant section shown)
  <div className="align-wide">
    {post.frontmatter.featuredImage && (
      <Img fluid={post.frontmatter.featuredImage.childImageSharp.fluid} />
    )}
  </div>

In the absence of conditional statements, all posts MUST have featured image or else it throws an error like “cannot read property ‘childImageSharp’ of null”.

Step 5: View in a Browser

After fresh starting server and viewed in a browser at localhost:8000, the featured image should appear just underneath the post title (see figure A, below).

Figure: Screenshot showing images added to markdown blog post, featured image (A) and inline image (B). Image source: Unsplash.
2. Adding Inline Images to Markdown Blog Posts

In a post created with Markdown, image(s) can be referred in the body of markdown file too. The image(s) are available for optimization with gatsby-remark-images and gatsby-resource-filesystem based the configuration in gatsby-config.js file (see previous section) and shown below:

//gatsby-config.js (only relevant section shown)
// ...
plugins: [
  `gatsby-plugin-sharp`,
  {
    resolve: `gatsby-transformer-remark`,
    options: {
      Plugins: [
        {
          resolve: `gatsby-remark-images`,
          options: {
            maxWidth: 1000,
          },
        },
      ],
    },
  },
  {
    resolve: `gatsby-source-filesystem`,
    options: {
      path: `${__dirname}/data/images`,
      name: `images`,
    },
  },
],
// ..

In the example above, the gatsby-source-filesystem plugin will make available the images in data/images folder for processing by gatsby-plugin-sharp and gatsby-remark-images plugins.

Step 3: Image Reference in Markdown Body

The image(s) can be referenced in the markdown body using its default images syntax:

<!---(data/posts/image-in-markdown-post/index.md)--->
....
![A Natural Beauty](./nature.jpg)
.....

In the above example assumes that the image nature.jpg is placed in the same folder with the post.

Step 4: View in a Browser

After fresh starting server and viewed in a browser at localhost:8000, the inline referenced image should appear in the body of the markdown post (see figure B, above).

Additional Information: Working With Images in Markdown Posts and Pages | Gatsby Docs

Image Alignments in Gatsby Markdown Posts

The Gatsby Tutorials Docs lacks information how images in Gatsby Markdown posts are styled and aligned. The How to Style Images With Markdown posts by Baron Schwartz is informative posts that discusses frequently used alignments in posts (eg., left-align, right-align, image-centering etc.) with respect to Hugo, whether the image alignment styles are also applied to Gatsby developed sites requires further evaluation.

Wrapping Up

In this learning-note posts, how images are added to pages and posts of Gatsby sites and optimized usinggatsby-image plugin with lazy-loading features for blazing fast performances were discussed.  A few use case examples adding GraphQL image queries to a page, non-page component and markdown posts (as featured image or inline image) were discussed.

Next Post: Adding Tags to Posts in Gatsby Site

Useful resources

While preparing this post, I have referred the following references extensively. Please to refer original posts for more detailed information.