GatsbyJS is awesome for creating fast, scalable, static websites that source content from anywhere. Whilst a lot of the major plugins for feeding data into your site support the Gatsby image optimisation plugin (gatsby-plugin-image) out-of-the-box, it’s not the case for all. Luckily for us, it’s simple to transform such images and make them utilise that optimising goodness! 🤤
Why bother?
In the relentless pursuit of owning a perfect Lighthouse score for your website, having properly optimised images is only going to help. The file sizes are smaller overall, so it’s quicker to load a webpage on devices with a dodgy internet connection. Also, using responsive images that are sized according to the device width, is going to ensure your images look great on all screen sizes. i.e. who wants to wait for an 8K image to load on their 4-inch iPhone SE? If you want to follow the best practices and recommendations for building a modern website, your images have got to be optimised. So let’s get it working for Gatsby!
Update project dependencies
In order to get things working, we need to first update our project dependencies. Run the following command in a Gatsby project directory to get all the necessary packages for optimising images:
yarn add gatsby-plugin-image gatsby-plugin-sharp gatsby-source-filesystem gatsby-transformer-sharp
yarn add gatsby-plugin-image gatsby-plugin-sharp gatsby-source-filesystem gatsby-transformer-sharp
And just in case you’re wondering, here’s what each plugin does:
gatsby-plugin-image
creates responsive, optimised images for our Gatsby site.
gatsby-source-filesystem
creates file nodes from our remote images.
gatsby-plugin-sharp
handles the processing of our images.
gatsby-transformer-sharp
creates the necessary nodes we can query
with GraphQL for our site in conjunction with gatsby-source-filesystem
.
Next, we should get our source plugin where we’ll pull content into our Gatsby site. For this tutorial we’ll use gatsby-source-ghost to pull content from a Ghost blog, but feel free to use any other source plugin.
yarn add gatsby-source-ghost
yarn add gatsby-source-ghost
Alternatively, check out this GitHub repo where you’ll find all the dependencies and the completed example project.
Now we just need to add our new plugins to the list of plugins in gatsby-config.js
:
require("dotenv").config({
path: `.env.${process.env.NODE_ENV}`,
});
module.exports = {
siteMetadata: {
siteUrl: "https://www.example.tld",
title: "gatsby-optimise-remote-images",
},
plugins: [
"gatsby-plugin-image",
"gatsby-plugin-sharp",
"gatsby-transformer-sharp",
{
resolve: "gatsby-source-ghost",
options: {
apiUrl: process.env.GHOST_API_URL,
contentApiKey: process.env.GHOST_API_KEY,
version: "v3",
},
},
],
};
require("dotenv").config({
path: `.env.${process.env.NODE_ENV}`,
});
module.exports = {
siteMetadata: {
siteUrl: "https://www.example.tld",
title: "gatsby-optimise-remote-images",
},
plugins: [
"gatsby-plugin-image",
"gatsby-plugin-sharp",
"gatsby-transformer-sharp",
{
resolve: "gatsby-source-ghost",
options: {
apiUrl: process.env.GHOST_API_URL,
contentApiKey: process.env.GHOST_API_KEY,
version: "v3",
},
},
],
};
Create remote nodes for GraphQL queries
Now with our dependencies installed, we can move on to creating the necessary nodes for our optimised remote images. With this, we can include the nodes in our GraphQL queries and access the necessary data to feed into the GatsbyImage
component, which will optimise our images.
Create the file gatsby-node.js
if you don’t have it already and add in the following code. Peep the comments if you want to know what’s going on! 👀
const { createRemoteFileNode } = require("gatsby-source-filesystem"); // We'll use this to create the file nodes from the remote images
exports.createSchemaCustomization = ({ actions }) => {
const { createTypes } = actions;
// creates a relationship between GhostPost and the File node for the optimized image
createTypes(`
type GhostPost implements Node {
remote_image: File @link
}
`); // change "GhostPost" to whatever type you're using from your source plugin
};
exports.onCreateNode = async ({
actions: { createNode },
getCache,
createNodeId,
node,
}) => {
// we need to verify that we're using the correct node created by our source plugin so we check its type and if it has a value
if (node.internal.type === `GhostPost` && node.feature_image !== null) {
// create the file node
const fileNode = await createRemoteFileNode({
url: node.feature_image, // URL of the remote image
getCache, // Gatsby cache
createNode, // helper function to generate the node
createNodeId, // helper function to generate the node ID
parentNodeId: node.id, // id of the parent node of the file
node,
});
// if the file node was created, attach the new node
if (fileNode) {
node.remote_image = fileNode.id;
}
}
};
const { createRemoteFileNode } = require("gatsby-source-filesystem"); // We'll use this to create the file nodes from the remote images
exports.createSchemaCustomization = ({ actions }) => {
const { createTypes } = actions;
// creates a relationship between GhostPost and the File node for the optimized image
createTypes(`
type GhostPost implements Node {
remote_image: File @link
}
`); // change "GhostPost" to whatever type you're using from your source plugin
};
exports.onCreateNode = async ({
actions: { createNode },
getCache,
createNodeId,
node,
}) => {
// we need to verify that we're using the correct node created by our source plugin so we check its type and if it has a value
if (node.internal.type === `GhostPost` && node.feature_image !== null) {
// create the file node
const fileNode = await createRemoteFileNode({
url: node.feature_image, // URL of the remote image
getCache, // Gatsby cache
createNode, // helper function to generate the node
createNodeId, // helper function to generate the node ID
parentNodeId: node.id, // id of the parent node of the file
node,
});
// if the file node was created, attach the new node
if (fileNode) {
node.remote_image = fileNode.id;
}
}
};
Now we can access our new remote images for optimisation in our GraphQL queries! 🎉 Put simply, all that we’re doing here is downloading the remote images to the filesystem, so that Gatsby can use the created files and optimise our images for us.
Usage in pages
Adding the optimised images to our site is exactly the same procedure we would use for adding a locally sourced image. We just need to update the GraphQL query. Let’s test this out by adding a list of our blog posts to the home page, with their featured images displayed as well. This is where we’ll make use of the GatsbyImage
component. Replace everything in src/pages/index.js
with this:
import * as React from "react";
import { Link, graphql } from "gatsby";
import { GatsbyImage } from "gatsby-plugin-image";
const IndexPage = ({ data }) => {
const blogPosts = data.allGhostPost.edges;
return (
<>
<h1>Blog Posts</h1>
<div>
<ul>
{blogPosts.map((post, i) => (
<li key={i}>
<Link to={post.node.slug}>
{/* GatsbyImage component to render our optimised image */}
<GatsbyImage
image={post.node.remote_image.childImageSharp.gatsbyImageData}
alt={post.node.title}
/>
<p>{post.node.title}</p>
</Link>
</li>
))}
</ul>
</div>
</>
);
};
export default IndexPage;
export const IndexQuery = graphql`
query blogListQuery {
allGhostPost(sort: { fields: [published_at], order: DESC }) {
edges {
node {
slug
title
published_at(formatString: "DD MMMM YYYY")
remote_image {
childImageSharp {
# this is the field which we'll pass into the GatsbyImage component
# we add the DOMINANT_COLOR placeholder to make a nice effect when lazy loading
gatsbyImageData(placeholder: DOMINANT_COLOR)
}
}
}
}
}
}
`;
import * as React from "react";
import { Link, graphql } from "gatsby";
import { GatsbyImage } from "gatsby-plugin-image";
const IndexPage = ({ data }) => {
const blogPosts = data.allGhostPost.edges;
return (
<>
<h1>Blog Posts</h1>
<div>
<ul>
{blogPosts.map((post, i) => (
<li key={i}>
<Link to={post.node.slug}>
{/* GatsbyImage component to render our optimised image */}
<GatsbyImage
image={post.node.remote_image.childImageSharp.gatsbyImageData}
alt={post.node.title}
/>
<p>{post.node.title}</p>
</Link>
</li>
))}
</ul>
</div>
</>
);
};
export default IndexPage;
export const IndexQuery = graphql`
query blogListQuery {
allGhostPost(sort: { fields: [published_at], order: DESC }) {
edges {
node {
slug
title
published_at(formatString: "DD MMMM YYYY")
remote_image {
childImageSharp {
# this is the field which we'll pass into the GatsbyImage component
# we add the DOMINANT_COLOR placeholder to make a nice effect when lazy loading
gatsbyImageData(placeholder: DOMINANT_COLOR)
}
}
}
}
}
}
`;
And that’s it! Run yarn develop in your terminal and navigate to http://localhost:8000 to see it all in action. Don’t forget to add some styles to make it all look 🔥