Gatsby with WordPress – Caching Downloaded Media Images to Reduce Build Time

Lots of different coloured watches

I have been working with WordPress as a back end CMS (one of the many micro services) and Gatsby to generate the website. To connect the two I am using a WordPress plugin called WP GraphQL, and a Gatsby plugin called Gatsby Source GraphQL. Everything was working brilliantly, apart from being able to use any images!

 

After much searching I found an article by Henrik Wirth from NeverNull explaining how to use the createResolvers function provided by Gatsby. His article walks though the process of getting all media items from the WordPress media library into Gatsby. Perfect, now they can be used throughout the build. Unfortunately this solution doesn’t cache the images, meaning everything is downloaded again. Putting pressure on both the Gatsby host – increased build time, and the WordPress host.

 

My solution builds on top of Henriks, adding that little bit of essential caching.

 

In the Gatsby Node File:

exports.createResolvers = ({
  actions,
  cache,
  createNodeId,
  createResolvers,
  getNode,
  store,
  reporter
}) => {
  const { createNode, touchNode } = actions;

  // Add all media libary images so they can be queried by
  // childImageSharp
  createResolvers({
    WPGraphQL_MediaItem: {
      imageFile: {
        type: `File`,
        async resolve(source, args, context, info) {
          if (source.sourceUrl) {
            let fileNodeID;
            let fileNode;
            let sourceModified;

            // Set the file cacheID, get it (if it has already been set)
            const mediaDataCacheKey = `wordpress-media-${source.mediaItemId}`;
            const cacheMediaData = await cache.get(mediaDataCacheKey);

            if (source.modified) {
              sourceModified = source.modified;
            }

            // If we have cached media data and it wasn't modified, reuse
            // previously created file node to not try to redownload
            if (cacheMediaData && sourceModified === cacheMediaData.modified) {
              fileNode = getNode(cacheMediaData.fileNodeID);

              // check if node still exists in cache
              // it could be removed if image was made private
              if (fileNode) {
                fileNodeID = cacheMediaData.fileNodeID;
                // https://www.gatsbyjs.org/docs/node-creation/#freshstale-nodes
                touchNode({
                  nodeId: fileNodeID
                });
              }
            }

            // If we don't have cached data, download the file
            if (!fileNodeID) {
              try {
                // Get the filenode
                fileNode = await createRemoteFileNode({
                  url: source.sourceUrl,
                  store,
                  cache,
                  createNode,
                  createNodeId,
                  reporter
                });

                if (fileNode) {
                  fileNodeID = fileNode.id;

                  await cache.set(mediaDataCacheKey, {
                    fileNodeID,
                    modified: sourceModified
                  });
                }
              } catch (e) {
                // Ignore
                console.log(e);
                return null;
              }
            }

            if (fileNode) {
              return fileNode;
            }
          }
          return null;
        }
      }
    }
  });

 

This works by:

  1. Finding the WP GraphQL Media Item Node –¬†WPGraphQL_MediaItem – and moves through all of the imageFile nodes within this.
  2. Checks to make sure there is a source URL for the image.
  3. Creates a cache ID based on the image and checks to see if an ID already exists. If one does, checks to see if this image is newer.
  4. If an image exists that isn’t newer, just give is a quick node refresh (so it isn’t deleted).
  5. If an image doesn’t exist, get the image and make a new cache ID.
  6. Return the node of the pre-existing or new image

 

A few things are required when using this resolver. When querying an image in the GraphQL query the following options need to be included:

  • sourceUrl
  • mediaItemId
  • modified

 

This means that a query to get a posts featured image would be written like this:

query GET_POSTS {
  posts {
    edges {
      node {
	featuredImage {
          sourceUrl
          mediaItemId
          modified
          imageFile {
            childImageSharp {
              fluid(maxWidth: 650) {
                base64
                aspectRatio
                src
                srcSet
                sizes
              }
            }
          }
        }
      }
    }
  }
}

 

Hopefully this helps you if you come across the same issue I did! This process can be altered to work with Author Avatars as well, save that precious bit of bandwidth.