When we build an application with Gatsby, we need data to make it functional. We can add the data directly within the application component or we store the data outside of the component and allow Gatsby to fetch it for us at build time.
While the former is not maintainable as the application grows, our focus will be on the latter. With Gatsby, we can fetch data from different sources which includes the file system, external APIs, databases, and CMSs like Contentful or WordPress.
Here, we will cover how to fetch data from a Gatsby configuration file and a source outside of Gatsby using GraphQL. We will do this by building a Gatsby blog site where we fetch MDX files from the file system and render the content as blog posts. You can see the finished project here.
At the end of this guide, we will understand the Gatsby data-fetching techniques and how to source data from any backend of our choice.
To follow this tutorial, ensure you are familiar with Gatsby and make sure you have Node.js, Git, and Gatsby CLI installed. It will also be beneficial if you are familiar with GraphQL. But if not, do not worry, because we’ll cover all you need in this tutorial.
Let’s start by downloading a starter project by running the following command in our computer terminal:
git clone https://github.com/Ibaslogic/gatsby-starter-data-fetching
Once finished, change the directory inside the project, cd gatsby-starter-data-fetching
and run npm install
. After that, open the project with a code editor and run gatsby develop
. Finally, we can open the project in the browser at http://localhost:8000.
If we take a look at the project structure, we should have this:
project_folder ├── node_modules ├── public ├── src │ ├── components │ │ ├── Footer.js │ │ ├── Header.js │ │ ├── Hero.js │ │ ├── layout.css │ │ └── ... │ ├── images │ ├── pages │ │ ├── ... │ │ ├── blog.js │ │ └── index.js │ └── menuItems.js ├── ... ├── gatsby-config.json └── ...
Be aware that the project CSS styles live in the components/layout.css
file, which includes the styles for the JSX we will create later in the project. Now that we’ve set up the project, it’s time to get started with data fetching.
siteMetadata
objectIn any web project, it is common that we have some pieces of data that we reuse across our site. For instance, we can use the site URL or social handles in multiple components of our application.
With Gatsby, we can store this set of data in the siteMetadata
object inside the Gatsby configuration file called gatsby-config.js
.
This object is also a good place to store any static data like the hero title and page description. That way, we can make updates in one central place without modifying the source code.
If we take a look at the home page of our starter project, we have the heading text, description, Twitter handle (in the Twitter URL) and the author name:
We will store this information in the siteMetadata
object. So, let’s open the gatsby-config.js
file in the root directory and update the siteMetadata
so we have the following:
module.exports = { siteMetadata: { title: `Gatsby tutorial`, description: `A step-by-step guide to data fetching with with Gatsby and GraphQL`, twitterHandle: "ibaslogic", author: "Ibas Majid", }, plugins: [], };
Save the file.
To fetch this data and use it in our application components, let’s talk about the Gatsby’s data layer.
In Gatsby, there are different ways to pull data into an application component. One of these ways is using GraphQL.
GraphQL is a query language that allows us to query the data that is available somewhere and use in our project files. In GraphQL, the location where our data is kept to fetch it is called the data layer. Fortunately, Gatsby has a built-in data layer powered by GraphQL.
For the data that we store inside the siteMetadata
of the configuration file, it automatically gets pulled into the Gatsby’s data layer. This will not be the case for data stored outside of Gatsby, which we will touch on later.
To explore the data layer and see the siteMetadata
info, we need to use a tool called GraphiQL. We can access this tool at localhost:8000/_graphql, but only when running gatsby develop
; be aware that there are three underscores in the URL.
Let’s open the tool via the link above and click on the Explorer button to view the Explorer pane. We should see this GraphiQL interface:
We can construct queries by toggling the dropdowns in the Explorer pane and clicking through the available data fields.
siteMetadata
contentIn the Explorer pane, we find the siteMetadata
object in the site dropdown field. There, we have two siteMetadata
s, but the one that holds our data is the one in blue. Let’s click on it and select the fields for the data we want.
In the case we can’t find the data in the Explorer pane, restart the development server and refresh the GraphiQL tool. This is necessary if we modify the gatsby-config.js
file. Now that we’ve checked the data, let’s click on the Execute Query button (that is, the Play button) to run the query.
As seen in the result window, we get the response containing our data in JSON format. Also, in the query editor, we can always delete the query name, MyQuery
, because it can cause issues if there is more than one query with the same name. So, the above query now looks like so:
query { site { siteMetadata { title description twitterHandle author } } }
Now that we have a query that fetches data, next we’ll learn how to use it to load data in our component.
StaticQuery
To load data using the GraphQL query, we will use either a page query or StaticQuery
. When we want to perform a query in a page component (i.e components that live inside the src/pages
folder), we can use any of the query types.
However, for nonpage components (that is, components that we can embed in other components like the Header
or Footer
), we must use only the StaticQuery
or its Hook version called useStaticQuery
.
useStaticQuery
to pull data into the Hero
componentIn the src/components/Hero.js
file, we have hardcoded the title, description, and Twitter handle. Instead, we’ll get this data from the data layer using the GraphQL query.
Remember, we can use StaticQuery
or useStaticQuery
to load the data. But in this project, we will use the useStaticQuery
Hook because of its flexibility and the options it provides to create a custom reusable Hook so we don’t need to manually write queries in multiple files that need the siteMetadata
info.
Now, open the Hero.js
file and import the useStaticQuery
function and the graphql
like so:
import { useStaticQuery, graphql } from "gatsby";
Then, add the following above the return
statement:
const data = useStaticQuery(graphql` query { site { siteMetadata { title description twitterHandle } } } `);
We invoke the useStaticQuery
function that accepts a graphql
tagged template with the query we created earlier added between two backticks. Notice that we are only fetching the data that we need; we are not fetching the author
because we don’t need it in this file.
Next, we‘ll render the data in the JSX like so:
const { title, description, twitterHandle } = data.site.siteMetadata; return ( <div className="hero"> <h1>{title}</h1> <p>{description}</p> <p>You can find{" "} <a href={`https://twitter.com/${twitterHandle}`} // ... > Ibas on Twitter </a> </p> </div> );
Notice how we get our data by destructuring the data.site.siteMetadata
object. Let’s save the file and test our project. If we modify and resave the data in the gatsby-config.js
file, we will see the changes in the frontend immediately.
useStaticQuery
HookIf we open the components/Footer.js
file, we also hardcoded the author name. And earlier, we learned how to fetch data from the data layer.
In this file, instead of writing another query to fetch the author
field from the siteMetadata
, we will write a custom reusable Hook for this functionality.
In the src
folder, let’s create a file called useSiteMetadata.js
and add the following code:
import { useStaticQuery, graphql } from "gatsby"; export const useSiteMetadata = () => { const data = useStaticQuery(graphql` query { site { siteMetadata { title description twitterHandle author } } } `); return data.site.siteMetadata; };
Here, we created a custom Hook called useSiteMetadata
and returned the siteMetadata
object holding our data. The query should be familiar. Also, notice that we’ve included all the data that we added in the siteMetadata
of the gatsby-config.js
file. Save the file.
Let’s revisit the components/Hero.js
and delete the query and the useStaticQuery
, graphql
import. Then, we import the useSiteMetadata
custom Hook and use it like so:
import React from "react"; import { useSiteMetadata } from "../useSiteMetadata"; const Hero = () => { const { title, description, twitterHandle } = useSiteMetadata(); return ( // ... ); }; export default Hero;
Let’s save and test our project. It should work.
Now, we can use the custom Hook in the components/Footer.js
file to fetch the author name from the Gatsby data layer like so:
import React from "react"; import { useSiteMetadata } from "../useSiteMetadata"; const Footer = () => { const { author } = useSiteMetadata(); return ( // ... <p> © {new Date().getFullYear().toString()} by {author} </p> // ... ); }; export default Footer;
Let’s save and test our project.
Up to this moment, we learned how to fetch data from a Gatsby file. Up next, we will source data that is outside of Gatsby.
The procedure for fetching data from the file system is similar to any other sources including the content management system (CMS).
Unlike the data in the siteMetadata
object that is automatically pulled into the GraphQL data layer, content from other sources will not be available by default.
Instead, we need a specific source plugin that communicates with the source and pulls data into the Gatsby’s GraphQL data layer. From there, we can write GraphQL queries in our component files to load the data we want.
In our project, we want to display a list of the blog posts from the file system on the blog page and the individual post. To do this, we must create post files.
Let’s start by creating a posts
folder in the root directory, then create .mdx
files and an images
folder. Our directory should look like so:
project_folder ... ├── node_modules ├── posts │ ├── images │ ├── first-post.mdx │ ├── second-post.mdx
Next, we can add some Markdown content in our files. In my case, I will add the following content inside the first-post.mdx
file:
--- title: "The first post" date: "2022-01-28" --- Lorem ipsum dolor sit amet consectetur adipisicing elit. ## Ipsam mollitia alias temporibus Explicabo eligendi necessitatibus 1. omnis earum nobis 2. architecto
Then, add the following content inside the second-post.mdx
:
--- title: "The second post" date: "2022-02-03" --- Lorem ipsum dolor sit amet consectetur adipisicing elit. ## Consectetur adipisicing Consectetur adipisicing elit - omnis earum nobis - architecto hello
In the MDX files, we added simple markdown content alongside the front matter where we specify the title
and date
. Let’s save our files.
As mentioned earlier, the .mdx
data will not be pulled automatically into the data layer. We will need the help of a source plugin called gatsby-source-filesystem
that will create File
nodes from the file system and allow us to query data about the files.
In addition to the source plugin, we will also need a transformer plugin called gatsby-plugin-mdx
that takes the File
nodes and creates new nodes called MDX
nodes that contain the data in the format we need.
The gatsby-plugin-mdx
requires the following extra dependencies to run: @mdx-js/mdx
and @mdx-js/react
.
In the terminal, let’s run the following commands to install the necessary plugins:
npm install gatsby-source-filesystem gatsby-plugin-mdx @mdx-js/mdx @mdx-js/react
Then, add them to the gatsby-config.js
file:
module.exports = { siteMetadata: { ... }, plugins: [ { resolve: `gatsby-source-filesystem`, options: { name: `posts`, path: `${__dirname}/posts/`, }, }, `gatsby-plugin-mdx`, ], };
This configuration tells Gatsby to source data from the path we specified. So, we must ensure that we have the files to fetch in the specified directory, else, we need to add a similar configuration object that captures where the data is located.
Let’s save the file and restart the development server.
If we refresh the GraphiQL IDE, we should see additional nodes added by the plugins that we installed. The source plugin creates the File
nodes and the transformer plugin transforms those nodes to the MDX
nodes, which is what we need.
Let’s now use the allMdx
to fetch all blog posts on our blog page and use the mdx
to fetch individual posts. If we explore some of the available fields on the nodes of the allFile
and allMdx
, we will have the following:
Notice that the name
field in the allFile
returns the same file names as the parent
in the allMdx
. This happens because when the transformer plugin creates a new MDX
node from the File
node, it allows us to access to the data of the parent File
node via the parent
field.
So, we can ignore the allFile
root field since we can get the same data plus extras (like the front matter or body content) via the allMdx
.
Remember, the focus here is on the allMdx
fields. If we explore some of the fields once again, we will have the following:
In the GraphQL data layer, our data is stored in the nodes
, which is returned as an array of objects as seen in the result window. Notice that we used the sort
argument to structure the posts in reverse-chronological order and we sorted by the front matter date.
Also notice how we get the data about the files in the File
node via the parent
field. The modifiedTime
field in the File
node returns the date the file was revised. We will later use it to distinguish a fresh post from an updated post.
Next, let’s render the data on the blog page of our site.
useStaticQuery
to pull blog posts into the blog pageAs we learned earlier, we can use either of StaticQuery
, its Hook version, or a page query to load data into a page component. Here, we will again use the Hook version. Later, we will learn how to use the page query.
In the src/pages/blog.js
file, include the necessary imports and add the generated query above the return
statement:
// ... import { useStaticQuery, graphql, Link } from "gatsby"; const BlogPage = () => { const data = useStaticQuery( graphql` query { allMdx(sort: { fields: frontmatter___date, order: DESC }) { nodes { frontmatter { title date(formatString: "MMMM D, YYYY") } timeToRead excerpt slug id parent { ... on File { modifiedTime(formatString: "MMMM D, YYYY") } } } } } ` ); const posts = data.allMdx.nodes; return ( <Layout> {/* ... */} </Layout> ); }; export default BlogPage;
Remember that the query returns our data as an array of objects in the nodes
. We will loop through the nodes
which we assigned to the posts
variable to render our posts. The JSX in the return
statement should now look like this:
return ( <Layout> <h1>Blog page.</h1> <ul> {posts.map((post) => { const { id, frontmatter, parent, timeToRead, excerpt, slug } = post; const { title, date } = frontmatter; const postStatus = date === parent.modifiedTime ? "Published " : "Updated "; return ( <li className="post_item" key={id}> <Link to={`/${slug}`}> <article> <h2>{title}</h2> <div className="post__meta"> <span> {postStatus} {parent.modifiedTime} </span>{" "} . <span>{timeToRead} min read</span> </div> <p>{excerpt}</p> </article> </Link> </li> ); })} </ul> </Layout> );
Let’s save our file and visit the frontend blog page. We should have our MDX posts listed.
This is great. Each of the posts is linked to the single post page using the Link
component as seen in the code above.
If we click any of the posts, we see a Gatsby error page. This is because the single post page doesn’t exist yet. Next, we will create this page to render the MDX body.
To fetch and render the MDX body, we must create a page template that Gatsby will use to programmatically create the single post pages. With the Gatsby file system route API, creating this template is a piece of cake.
Let’s open the src/pages
directory and create a file called {mdx.slug}.js
. Inside the file, add the following starting code:
import React from "react"; import Layout from "../components/layout"; const BlogPost = () => { return ( <Layout> <article className="single__post">Single post page</article> </Layout> ); }; export default BlogPost;
Now, Gatsby will use this single template to dynamically render a single post page based on the MDX slug.
As stated earlier, we will use the mdx
field to fetch individual posts instead of the allMdx
that fetches all posts. If we open the GraphiQL IDE and explore the fields on the mdx
root, we will have something like this:
The way the single post works is that we must pass arguments to the mdx
field to change the data we get back from the query. Here we are changing the data based on the slug.
If we manually replace the "second-post"
slug with "first-post"
, we get back the data in the first-post.mdx
file.
Now, instead of replacing the slug manually, we can use the query variables to dynamically send the slug along with the request. If we implement the query variables in the IDE, our query should look like so:
GraphiQL also provides a section to add the slug to be passed to our query. Then in the query, we must define and use the variable with $
prepended to the slug. However, in the page template file, Gatsby automatically makes the individual page slug available to use as query variables in our query.
To use the query variable in our query, we must construct a page query instead of a static query. In this page query, we must export a GraphQL query assigned to a variable. And, this type of query is placed outside of the component.
Let’s see how it works. Open the pages/{mdx.slug}.js
file and add the constructed query above the component definition so we have the following:
// ... import { graphql } from "gatsby"; import { MDXRenderer } from "gatsby-plugin-mdx"; export const query = graphql` query ($slug: String) { mdx(slug: { eq: $slug }) { frontmatter { title date(formatString: "MMMM D, YYYY") } timeToRead parent { ... on File { modifiedTime(formatString: "MMMM D, YYYY") } } body } } `; const BlogPost = () => { return ( // ... ); }; export default BlogPost;
Let’s ensure we import the graphql
and MDXRenderer
from their respective modules. We will use the MDXRenderer
to render the MDX body content. Notice we also queried for the body
field above.
When we use a page query, Gatsby automatically injects the result of the query into the page component as a data
prop. Let’s use this prop to render our content in the JSX by updating the component so we have the following:
// ... const BlogPost = ({ data }) => { const { frontmatter, parent, timeToRead, body } = data.mdx; const { title, date } = frontmatter; const postStatus = date === parent.modifiedTime ? "Published on" : "Updated on"; return ( <Layout> <article className="single__post"> <header> <h1>{title}</h1> <span className="post__meta"> {postStatus} {parent.modifiedTime} <span> . </span> {timeToRead} min read{" "} </span> </header> <div> <MDXRenderer>{body}</MDXRenderer> </div>{" "} </article> </Layout> ); }; export default BlogPost;
Save the file and revisit one of the single post pages. We should see the content rendered like so:
In a markdown or MDX file, we can add images to our posts via front matter or within the markdown body. But with Gatsby, by default, adding images in the MDX file will not render on the post page.
We must provide support for Gatsby to render these images instead, which is necessary for Gatsby to provide an optimized version of the images. Let’s add these images.
In the posts/first-post.mdx
, add a front matter field called featured
and an inline markdown image so we have the following:
--- title: "The first post" date: "2022-01-28" featured: "./images/first-post-featured.png" --- ![first post image](./images/first-post-content.png)
The image path is relative to the page file. Remember we have an images
folder in the posts
folder. Open this Gatsby project on GitHub, grab the images, and save them in the images
folder.
Next, open the posts/second-post.mdx
file and update like so:
--- title: "The second post" date: "2022-02-03" featured: "./images/second-post-featured.jpg" --- ![second post image](./images/second-post-content.png)
Let’s save our files.
Next, we can install a couple of plugins to tell Gatsby about these images. Head over to the terminal and run the following command:
npm install gatsby-plugin-image gatsby-plugin-sharp gatsby-remark-images gatsby-transformer-sharp
Here, each plugin does the following:
gatsby-plugin-image
provides an image component called GatsbyImage
to render our featured imagegatsby-transformer-sharp
creates a special node field for processing the featured imagesgatsby-remark-images
processes images within the markdown bodygatsby-plugin-sharp
processes images and reduces image sizeOnce installed, open the gatsby-config.js
file and add the plugins so it looks like this:
module.exports = { siteMetadata: {...}, plugins: [ `gatsby-plugin-image`, `gatsby-plugin-sharp`, `gatsby-transformer-sharp`, { resolve: `gatsby-source-filesystem`, options: {...}, }, `gatsby-remark-images`, { resolve: `gatsby-plugin-mdx`, options: { gatsbyRemarkPlugins: [ { resolve: `gatsby-remark-images`, options: { maxWidth: 750, linkImagesToOriginal: false, }, }, ], }, }, ], };
Notice that we’ve added the gatsby-remark-images
as a string and within the options
field of the gatsby-plugin-mdx
.
Let’s save the file and restart the development server. We should see the body image appear in the post.
Since the featured image lives in the front matter, we will fetch it using the GraphQL query. If we refresh the GraphiQL IDE, we should have an additional field for the featured that looks like so:
As seen above, the gatsbyImageData
field contains information about our featured image, and it is located in the childImageSharp
field.
GatsbyImage
componentLet’s open the src/pages/{mdx.slug}.js
file and update the front matter field of the query to include the featured
field like so:
frontmatter { title date(formatString: "MMMM D, YYYY") featured { childImageSharp { gatsbyImageData } } }
Next, we will destructure the featured
object from the frontmatter
. So in the file, find the following line:
const { title, date } = frontmatter;
And update to this:
const { title, date, featured } = frontmatter;
After that, import the GatsbyImage
component like so:
import { GatsbyImage } from "gatsby-plugin-image";
And then use the GatsbyImage
component to render the featured image like so:
return ( <Layout> <article className="single__post"> <header> {/* ... */} <GatsbyImage image={featured.childImageSharp.gatsbyImageData} alt={title} /> </header> <div> <MDXRenderer>{body}</MDXRenderer> </div>{" "} </article> </Layout> );
Save the file and revisit a single post page; we should see the featured image rendered.
I am glad we are here. We learned how to fetch data from the file system and the siteMetadata
object of the Gatsby configuration file using GraphQL. Now, we can replicate what we learned and fetch data from any source of our choice using their specific plugins from the Gatsby plugins page.
If you like this tutorial, ensure you share it around the web. And, if you have questions or contributions, I’m in the comment section.
Here is the project source code on GitHub.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your site. Instead of guessing why problems happen, you can aggregate and report on problematic GraphQL requests to quickly understand the root cause. In addition, you can track Apollo client state and inspect GraphQL queries' key-value pairs.
LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. Start monitoring for free.Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowThe useReducer React Hook is a good alternative to tools like Redux, Recoil, or MobX.
Node.js v22.5.0 introduced a native SQLite module, which is is similar to what other JavaScript runtimes like Deno and Bun already have.
Understanding and supporting pinch, text, and browser zoom significantly enhances the user experience. Let’s explore a few ways to do so.
Playwright is a popular framework for automating and testing web applications across multiple browsers in JavaScript, Python, Java, and C#. […]