Load Gatsby ImageSharp from Image URL Source

May 04, 2020

“Gatsby is an open source, modern website framework that builds performance into every site by leveraging the latest web technologies such as React and GraphQL. Create blazing fast apps and websites without needing to become a performance expert” - gatsbyjs.com.

In this article we will talk about Gatsby Image, Gatsby ImageSharp and how to load a Gatsby Image Sharp from image URL source, but if you are new to Gatsby, I highly recommend going through their official website first and familiarize yourself with how Gatsby works.

Gatsby ImageSharp uses gatsby-image, the “official image component used in building Gatsby websites” - using-gatsby-image.gatsbyjs.org. This package provides the most optimized image loading performance possible for Gatsby websites, it will resize images for each device size and screen resolution, implementing progressive image loading - uses the “blur-up” effect, loading a tiny and pixelated version of the image to show while the full image is loaded.

        Blur-up effect while image is loading:

image1

        Full image loaded:

image2

Gatsby-image component uses gatsby-plugin-sharp for image transformation and is used with Gatsby’s GraphQL queries, in the following way:

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

export default ({ data }) => (
  <div>
    <h1>Hello gatsby-image</h1>
    <Img fixed={data.file.childImageSharp.fixed} />
  </div>
)

export const query = graphql`
  query {
    file(relativePath: { eq: "blog/avatars/kyle-mathews.jpeg" }) {
      childImageSharp {
        # Specify the image processing specifications right in the query.
        # Makes it trivial to update as your page's design changes.
        fixed(width: 125, height: 125) {
          ...GatsbyImageSharpFixed
        }
      }
    }
  }
`

https://www.gatsbyjs.org/packages/gatsby-image/#how-to-use

As you can see, this query is loading an image from a local project, using the relative path of the image (relativePath: { eq: "blog/avatars/kyle-mathews.jpeg" }).

In case you don’t have images locally saved, if you want to load images using URLs or to fetch data and image URLs from an API, you will see that you will not be able to generate Image Sharp directly from that URL, because Image Sharp works only with File nodes.

For generating ImageSharp from URL, you have to create an image object from image URL, turn the object into a new Gatsby node, download the image in a local file and create a new ImageSharp.

Let’s say that we have an API that returns the following JSON:

projects: [
  {
    id: 1,
    title: 'First Project',
    images: [
      {
        id: 1,
        source: ‘https: //image-link.jpg’
      },
      {
        id: 2,
        source: ‘https: //image-link.jpg’
      },
      ...
    ]
  },
  {
    ...
  }
]

We will make all the required changes in the gatsby-node.js file. By using this file during the bootstrapping phase, Gatsby collects sourceNodes, onCreateNode, etc., and generates a GraphQL schema from it.

In sourceNodes we will create the image object (using the function createImageObjectFromURL), will turn it into a gatsby node (using turnImageObjectIntoGatsbyNode) and will create a new node (using createNode).

exports.sourceNodes = async ({ actions, createNodeId }) => {
  const turnImageObjectIntoGatsbyNode = (image, project) => {
    const content = {
      content: project.title,
      ['image___NODE']: createNodeId(`project-image-{${project.id}}`),
    };
    const nodeId = createNodeId(`image-{${image.id}}`);
    const nodeContent = JSON.stringify(image);
    const nodeContentDigest = crypto
         .createHash('md5')
         .update(nodeContent)
         .digest('hex');

    const nodeData = {
      ...image,
      ...content,
      id: nodeId,
      parent: null,
      children: [],
      internal: {
        type: 'Image',
        content: nodeContent,
        contentDigest: nodeContentDigest,
      },
     };
    return nodeData;
  };

  const createImageObjectFromURL = (url) => {
    const lastIndexOfSlash = url.lastIndexOf('/');
    const id = url.slice(lastIndexOfSlash + 1, url.lastIndexOf('.'));
    return { id, image: id, url };
  };

  const { createNode } = actions;
  const projects = await service.getProjects();

  projects.forEach((project) => {
    project.images.map((image) => {
      const imgObj = createImageObjectFromURL(image.source);
      const nodeData = turnImageObjectIntoGatsbyNode(imgObj, project);
      createNode(nodeData);
    });
  });
};

In onCreateNode we will use the function createRemoteFileNode from gatsby-source-filesystem to download the image in a local file, obtaining a reference to the file afterwards. To generate an Image Sharp we will use gatsby-plugin-sharp and gatsby-transformer-sharp, and because ImageSharp only works if the image is a File node in the GraphQL layer, we have to link our previous created node to File node.

const { createRemoteFileNode } = require('gatsby-source-filesystem');

...

exports.onCreateNode = async ({
  node, actions, store, getCache, createNodeId
}) => {
  if (node.internal.type === 'Image') {
    const { createNode } = actions;

    /* Download the image and create the File node. Using gatsby-plugin-sharp and gatsby-transformer-sharp the node will become an ImageSharp. */

    const fileNode = await createRemoteFileNode({
       url: node.url, // string that points to the URL of the image
       parentNodeId: node.id, // id of the parent node of the fileNode you are going to create
       store, // Gatsby's redux store
       getCache, // get Gatsby's cache
       createNode, // helper function in gatsby-node to generate the node
       createNodeId, // helper function in gatsby-node to generate the node id
    });

    if (fileNode) {
      // link the File node to Image node at field image
      node.image___NODE = fileNode.id;
    }
  }
 };

So, the GraphQL query to obtain the images as Image Sharp will look like this:

{
  images: allImage {
    edges {
      node {
        url
        image {
          publicURL
          childImageSharp {
            fluid (maxWidth: 1440) {
              ...GatsbyImageSharpFluid
            }
          }
        }
      }
    }
  }
}

Important: Even if all the changes performed for creating Images Sharp were done in gatsby-node.js, the previous query can be done only in the file where the images are rendered. This is because gatsby-node.js only creates pages, paths and assigns objects in context, which you can use in your page/template as a pageContext object to retrieve the data you need. As a result, GatsbyImageSharpFluid doesn’t get recognized as a valid fragment.

As you can see, Gatsby’s features regarding image loading performance can be used just as easily using local data or data that comes from an API, having the same effect; the only additional thing that you have to do is to translate the image URL in such way that it gets rendered as a gatsby image.

Featured Articles