Even though a picture is worth a thousand words, it costs a pretty penny to store and deliver images online. Because of this, many modern web-development tools aim to minimize the impact of images, and other media assets, on site performance.
This post shows you how to build an optimized webpage by leveraging the gatsby-transformer-cloudinary plugin and gatsby-image. We’ll upload local images to a remote content delivery network (Cloudinary), then subsequently source, and transform, the responsive remote images in a GatsbyJS project.
Sandbox
We completed this project on Codesandbox, and you can find it here. You need to provide your Cloudinary credentials as environment variables in the sandbox by creating a .env
file in the root directory. Specify the environment variables as shown in .env.example
.
https://codesandbox.io/s/using-remote-images-in-gatsby-w-gatsby-image-live-yqhxq
Here are the steps we’ll follow to use remote images in a gatsby project with gatsby-image:
- Install Gatsby.js and its dependencies.
- Set up the project configuration and layout.
- Handle single- or multiple-image queries from Cloudinary through gatsby-transformer-cloudinary.
- Optimize the sourced images with Cloudinary and lazy-load them with gatsby-image.
- Transform the images using Cloudinary right in the graphql image query.
- Design a responsive layout and typography with Chakra-ui.
Typically, with gatsby-image, images are stored locally in the project. To utilize external images, a hosted image is required and used with a normal HTML <img/>
element. With gatsby-transformer-cloudinary, we can utilize remote images in gatsby-image.
Prerequisite and Installation
This project requires knowledge of JavaScript, React, and the basics of Gatsby.js. You install Gatsby.js and other packages with Node.js and its package manager npm, or Yarn, a viable alternative for it.
We install Gatsby.js globally with npm, and create a new project using the command
1npm install -g gatsby-cli && gatsby new gtc-demo
To give you a jump-start, Gatsby scaffolds new projects with a starter, which comprises several pages and modules, and we’ll remove them when they’re obsolete.
We proceed to install the required dependencies using the command:
1cd gtc-demo && npm i --save gatsby-transformer-cloudinary dotenv @chakra-ui/gatsby-plugin @chakra-ui/react @emotion/react @emotion/styled framer-motion
Project configuration setup
Sign up for a Cloudinary account. Cloudinary offers a free tier, which is more than adequate for small to medium projects. Once signed up, note your cloud name, API key, and API secret for later use.
Gatsby touts two types of plugins:
- Source plugins that fetch data from many sources into Gatsby projects.
- Transformer plugins that convert sourced data to usable formats.
gatsby-transformer-cloudinary uploads images from a local directory to Cloudinary, and transforms the returned image to a format usable by gatsby-image. Cloudinary serves as a drop-in replacement for gatsby-plugin-sharp, and harnesses gatsby-image’s native image-processing capabilities.
Gatsby ships with a configuration file named gatsby-config.js
in the root of the project. We’ll set up gatsby-transformer-cloudinary, and @chakra-ui/gatsby-plugin, by updating the array of plugins in gatsby-config.js
, to include this:
1require("dotenv").config()2 module.exports = {3 siteMetadata: {4 title: `Gatsby x Cloudinary`,5 description: `Serve remote images in your Gatsby app using gatsby-image`,6 author: `@gatsbyjs`7 },8 plugins: [9 `gatsby-plugin-react-helmet`,10 {11 resolve: `gatsby-source-filesystem`,12 options: {13 name: `cloudinary-images`,14 path: `${__dirname}/src/remote-images`15 }16 },17 {18 resolve: "@chakra-ui/gatsby-plugin",19 options: {20 isUsingColorMode: true21 }22 },23 {24 resolve: "gatsby-transformer-cloudinary",25 options: {26 cloudName: process.env.CLOUDINARY_CLOUD_NAME,27 apiKey: process.env.CLOUDINARY_API_KEY,28 apiSecret: process.env.CLOUDINARY_API_SECRET,29 uploadFolder: "gtc-art-gallery"30 }31 }32 ]33 }
In the configuration file, gatsby-source-filesystem, a source plugin sources file nodes into the Gatsby data layer. Here, we’ve sourced all the images in a folder, which are uploaded once to Cloudinary on build.
We created the remote-images
folder in the src
directory of the project, and uploaded all the images into the folder.
We proceed to create a .env file in the project’s root directory, and add our Cloudinary credentials as specified below:
1# Find this at https://cloudinary.com/console/settings/account2 CLOUDINARY_CLOUD_NAME=xxxxx34 # Generate an API key pair at https://cloudinary.com/console/settings/security5 CLOUDINARY_API_KEY=xxxxxxxxxxxxxx6 CLOUDINARY_API_SECRET=xxxxxxxxxxxxxxxxxxx
Next, we start the development server on localhost:8000
by running the command:
1gatsby develop
All the images in the remote-images folder are uploaded to Cloudinary, and added to file nodes for the returned URLs are created in Gatsby’s graphql layer.
Pages layout design
The layout.js file in the src/components
directory specifies the pages’ design, which persists across the site, and can contain UI elements like headers and footers.
We edit the layout to add a header, which contains the navigation bar and body of the website. First, we import all required dependencies with:
1import React from "react"2 import PropTypes from "prop-types"3 import { graphql, useStaticQuery } from "gatsby"4 import Header from "./header"5 import { Box, Text, Link } from "@chakra-ui/react"
Next, we define the layout component, and fetch the site title defined in gatsby-config.js using useStaticQuery
, a hook that utilizes Gatsby.js's StaticQuery API to make GraphQL queries in Gatsby.js components.
1const Layout = ({ children }) => {2 const data = useStaticQuery(graphql`3 query SiteTitleQuery {4 site {5 siteMetadata {6 title7 }8 }9 }10 `)11 return (12 <Box>13 <Header siteTitle={data.site.siteMetadata.title} />14 <Box width={["90%", "90%", "80%"]} mx={"auto"}>15 <main>{children}</main>16 <Text mt={10}>17 For this demo, the amazing images here by great artists were all18 sourced from{" "}19 <Link20 href={"https://unsplash.com/"}21 target={"_blank"}22 color="teal.500"23 >24 Unsplash25 </Link>26 </Text>27 </Box>28 </Box>29 )30 }31 Layout.propTypes = {32 children: PropTypes.node.isRequired33 }34 export default Layout
In the snippet above
The
<Header/>
custom component represents the navigation bar. Chakra-ui styles the layout with different responsive widths and multiple breakpoints: 90 percent, 90 percent, and 80 percent for mobile, tablet, and desktop devices, respectively.The
mx
property sets the page’s horizontal-margin property toauto
, centering the page content regardless of the width.
Next, we add the site title and the navigation bar’s dimensions by updating the Header
functional component in src/components/header.js
, as follows.
We import the required dependencies, and UI components.
1import { Link as GatsbyLink } from "gatsby"2 import PropTypes from "prop-types"3 import React from "react"4 import { Box, Flex, Heading, Text, Button } from "@chakra-ui/react"
Next, we create a Header
component to render a minimal navigation bar with a menu link.
1const Header = ({ siteTitle }) => {2 return (3 <Flex4 as="nav"5 align="center"6 justify="space-between"7 wrap="wrap"8 px={["0.5em", "0.5em", "1.5em"]}9 py={["1em", "1em", "1.5em"]}10 bg="blue.900"11 color="white"12 >13 <Flex align="flex-start">14 <Heading as="h1">15 <GatsbyLink to="/">16 <Box color={"white.800"}>17 <Text fontSize={["md", "md", "lg"]}>{siteTitle}</Text>18 </Box>19 </GatsbyLink>20 </Heading>21 </Flex>22 <Flex align="flex-end">23 <Button colorScheme={"blue"} mr={2} size={"sm"}>24 <GatsbyLink to="/">Home</GatsbyLink>25 </Button>26 </Flex>27 </Flex>28 )29 }
Lastly, we define the proptypes, the component's default prop values, and export the Header
component.
1Header.propTypes = {2 siteTitle: PropTypes.string3 }4 Header.defaultProps = {5 siteTitle: ``6 }7 export default Header
We use Chakra-ui components and styles to build a navigation bar, accompanied by the site title and a button to return to the homepage with a click.
Pages creation
We have two pages: a page with a responsive banner image, and a page with a responsive image gallery.
Responsive banner image
In src/pages/index.js
, we’ll import the required dependencies and make a GraphQL query for a Cloudinary Image.
First, we import the required dependencies and make a GraphQL query for the fluid images:
1import React from "react"2 import { graphql, Link, useStaticQuery } from "gatsby"3 import Layout from "../components/layout"4 import SEO from "../components/seo"5 import Image from "gatsby-image"6 import { Box, Button, Heading, Text } from "@chakra-ui/react"78 const IndexPage = () => {9 // fetch images10 const data = useStaticQuery(graphql`11 query BannerImage {12 bannerImage: file(name: { eq: "7" }) {13 cloudinary: childCloudinaryAsset {14 fluid(transformations: ["e_grayscale"], maxWidth: 1500) {15 ...CloudinaryAssetFluid16 }17 }18 }19 }20 `)21 // Assign the returned images to variables.22 const bannerImage = data.bannerImage.cloudinary.fluid2324 return (25 // component JSX to go in here26 )
The useStaticQuery
hook is for querying the images in this component. The ...CloudinaryAssetFluid
fragment returns a fluid image data that contains aspect ratio, the base64 image, src, sizes, and srcSet. Where we must specify those data fields in a query, fragments take the place of the GraphQL fields.
Just as we added e_grayscale
transformation in the query, multiple Cloudinary transformations including format and quality types, can be utilized. You can find transformation options here.
Next, we return the JSX elements to render the page. The elements are Chakra-ui components.
1return (2 <Layout>3 <SEO title="Home" />4 <Box mb={[10, 20, 100]}>5 <Heading size={"xl"} m={3} textAlign={"center"}>6 Responsive Banner Image7 </Heading>8 <Box>9 <Image fluid={bannerImage} />10 </Box>11 </Box>12 <Text my={5}>13 Click any of the buttons below to see the gallery or single Image with14 the <i>getFluidImageObject</i> API15 </Text>16 <Box>17 <Button colorScheme={"teal"} mr={10} mb={[2, 0, 0]}>18 <Link to="/gallery"> Gallery Images</Link>19 </Button>20 </Box>21 </Layout>22 )23 }24 export default IndexPage
We pass the fluid image data to the gatsby-image <Image/>
component to render the image.
To see the lazy loading in action, we refresh the page or look in the browser’s network tab. Since subsequent refreshes returned cached image versions, we have to do a hard refresh of the page or throttle the network to spot the lazy loading.
Gallery Images
Like the single responsive image, we’ll create a page by creating a file in the src/pages directory called gallery.js
. Gatsby.js handles the automatic page creation of any file added to the src/pages
directory.
We proceed to import the required dependencies and query all the uploaded images.
1import React from "react"2 import Image from "gatsby-image"3 import { Box, Heading, SimpleGrid } from "@chakra-ui/react"4 import { graphql, useStaticQuery } from "gatsby"5 import Layout from "../components/layout"6 import SEO from "../components/seo"78 const SinglePage = () => {9 const data = useStaticQuery(graphql`10 query GalleryImages {11 listImages: allCloudinaryAsset(limit: 9) {12 images: edges {13 node {14 fluid {15 ...CloudinaryAssetFluid16 }17 }18 }19 }20 }21 `)22 const galleryImages = data.listImages.images
Next, we render the image gallery component using SimpleGrid
- a Chakra-ui components to create grids using CSS-grid.
1return (2 <Layout>3 <SEO title={"single"} />4 <Box mx={"auto"} my={10}>5 <Heading textAlign={"center"} size={"xl"} mb={10}>6 Optimized Gallery Images7 </Heading>8 <SimpleGrid columns={[1, 2, 3]} spacing={2}>9 {galleryImages.map((val, index) => (10 <Box11 key={index}12 p={3}13 m={2}14 my={"auto"}15 shadow="md"16 borderWidth="1px"17 rounded={"lg"}18 >19 <Image fluid={val.node.fluid} />20 </Box>21 ))}22 </SimpleGrid>23 </Box>24 </Layout>25 )26 }27 export default SinglePage
Using Chakra-ui components, we loop through the returned list of fluid images and render each in a gatsby-image component.
You can see the final result here, and the pages look like this:
How about we look at how to use remote Images in gatsby-image without the GraphQL query overhead? We’ll discuss that in a subsequent post.
Summary
In this post, we saw how to utilize remote optimized images from Cloudinary in a Gatsby site. We render the images using Gatsby.js's gatsby-image component to provide performance optimizations during the site's build process.
You may find these resources useful.