Today I'll be sharing how to fetch blog data from a content management system (CMS) and display it in a Next.js application. I'm using Contentful if this tutorial, but the three different technics that will be covered should work with other CMSs as well.
Setting Up Contentful
First, we need to set up Contentful. Visit https://www.contentful.com/ to create a free account if you don't already have one. Click on "Content Type" then "Add Content Type". Give your content type the name "blog test". Contentful will automatically generate your API identifier, and the description is optional.
Click "create" when you're done.
It's now time to create the data we'll be fetching. Click on the content type you created. Now click on the blue button titled "Add field". Contentful offers several content types such as images, rich text, and dates, to name just a few, but to keep things simple, we're only going to create a text field in this tutorial. So click on "Text" and name your field "title". Click on the green button "create and configure".
You should now see a few different options in the modal header. Choose "Validation" and make sure "required field" and "unique field" are checked. Checking "required field" means Contentful won't let you publish new content withing providing data for this field. "unique field" means that Contentful won't allow you to publish an entry if another entry has identical data for the same field. This is especially important for fields like slugs. In our case, we want our title field to be unique because we'll also be using the data from this field as a key when we map over our data.
The last thing we need to do before we start writing code is to create a few different titles to display in our app. Click on "Content" in your dashboard header, then click the blue button titled "Add blog test". Fill out the title then click "Publish". Repeat a few more times until you have at least 5 different titles.
Write your GraphQL query
We're be fetching our contentful data using GraphQL.
Contentful offers a GraphiQL playground so we'll be building our query there before pasting it into our app.
Go to: https://graphql.contentful.com/content/v1/spaces/[YOUR_SPACE_ID]/explore?access_token=[YOUR_ACCESS_TOKEN]
To get your Space Id and Access Token, from your dashboard, select Settings-->API keys. You can use an existing key if you already have one, or create a new key by clicking "Add API key".
Type the following code in the left column of your GraphQl API playground:
{
blogTestCollection{
items{
title
}
}
}
Now click the play button.
You should see something like this show up on the right:
{
"data": {
"blogTestCollection": {
"items": [
{
"title": "I'm running out of ideas"
},
{
"title": "How to think of good titles"
},
{
"title": "Another amazing title"
},
{
"title": "A very interesting title"
},
{
"title": "How to fetch data in Next.js"
}
]
}
}
}
The titles should match the titles you added to Contentful, with the last title you added at the top. Keep your playground open as we'll come back to it later.
Setting up our Next.js App
Create a new Next.js project by typing npx create-next-app
or yarn create-next-app
in your code editor's terminal. You'll be asked to choose a name for your app. Once everything is done loading, cd into your project folder. Then open pages/index.js
and replace its contents with the code below.
import { GraphQLClient, gql } from "graphql-request";
const endpoint = `https://graphql.contentful.com/content/v1/spaces/${process.env.SPACE_ID}`;
const graphQLClient = new GraphQLClient(endpoint, {
headers: {
authorization: `Bearer ${process.env.ACCESS_TOKEN}`,
},
});
export async function getPosts() {
const postsQuery = gql`
{
blogTestCollection {
items {
title
}
}
}
`;
const data = await graphQLClient.request(getPosts);
return data.blogTestCollection.items;
}
const Home = () => {
console.log(titles);
return <div></div>;
};
export default Home;
Run either npm i graphql-request
or yarn add graphql-request
to install graphql-request
. Next, create an .env.local file at the root of your project and add your SPACE_ID and ACCESS_TOKEN to the file.
You may notice that the query in getTitles is the same one we tested in the GraphiQL playground earlier. Although not required, I like to test my queries in the playground first to make sure they are working as expected before bringing them into my app code.
Fetching Data with getStaticProps
getStaticProps is probably the most popular option for fetching blog data. The data is (more or less) only fetched as build time, making it the fastest of the three options in this article. The downside to this is that you might not be showing your viewers your most recent blog data. It's possible to re-fetch data using the revalidate
prop together with getStaticProps, but there is still a chance of users coming across your website and not seeing your most recent data. For blogs, this usually isn't a huge problem. If this is your case, getStaticProps might be a good option for you.
In pages/index.js add your getStaticProps function, and pass posts
to Home as a prop. Then, map over your posts
array to display your titles on the page. In this example, I didn't add any styling but feel free to add whatever styles suit your fancy. Next, add revalidate:60
to getStaticProps. This will ask Next will regenerate the page in the background after 60 seconds. The new page will be saved to the cache and available for your next visiter. Learn more about incremental static regeneration here
...
export async function getPosts() {
const postsQuery = gql`
{
blogTestCollection {
items {
title
}
}
}
`;
const data = await graphQLClient.request(postsQuery);
return data.blogTestCollection.items;
}
export async function getStaticProps() {
const posts = await getPosts();
return {
props: { posts },
revalidate: 60,
};
}
const Home = ({ posts }) => {
console.log(posts);
return (
<div>
{posts.map((post) => (
<p key={post.title}>{post.title}</p>
))}
</div>
);
};
export default Home;
...
Now run npm run dev
or yarn dev
to make sure everything is working as expected. Hopefully, you'll see your five or so titles show up on your screen.
Fetching Data with getServerSideProps
getServersideProps is a good option if you're willing to sacrifice a bit of speed in order to display your most recent blog data. getServerSide props will rerun on every render, so the data displayed on the page will be the most recent data available at the time when your user first opens your website.
The code for getServerSideProps
and getStaticProps
is very similar. Starting with the code from the previous step, simply change your getStaticProps
function's name to getServerSideProps
, and remove 'revalidate:60'. We no longer need to ask Next to regenerated the page in the background because the data will be fetched on every render.
...
export async function getServerSideProps() {
const posts = await getPosts();
return {
props: { posts },
};
}
...
Fetching Data with useEffect
For developers coming from react, useEffect might seem like a more familiar option than the two options above. getStaticProps and getServerSideProps have greatly diminished the need to fetch data in useEffect, but getStaticProps and getServerSideProps can only be used in pages, so if you'd like to fetch data from a component instead of passing it down as props from a page, useEffect might be a good option for you. In general, I'd recommend choosing one of the two other options above unless there's a specific reason you need to fetch your data on the client side.
Starting with the code from the previous step, completely remove the getServerSideProps
function as well as the props passed to Home
. Then, import useEffect and useState and set up your useEffect hook. Lastly, change SPACE_ID, and ACCESS_TOKEN to NEXT_PUBLIC_SPACE_ID and NEXT_PUBLIC_ACCESS_TOKEN. Adding NEXT_PUBLIC to your environment variables tells Next it's ok to expose them to the browser.
import { GraphQLClient, gql } from "graphql-request";
import { useEffect, useState } from "react";
const endpoint = `https://graphql.contentful.com/content/v1/spaces/${process.env.NEXT_PUBLIC_SPACE_ID}`;
const graphQLClient = new GraphQLClient(endpoint, {
headers: {
authorization: `Bearer ${process.env.NEXT_PUBLIC_ACCESS_TOKEN}`,
},
});
export async function getPosts() {
const postsQuery = gql`
{
blogTestCollection {
items {
title
}
}
}
`;
const data = await graphQLClient.request(postsQuery);
return data.blogTestCollection.items;
}
const Home = () => {
const [posts, setPosts] = useState([]);
useEffect(() => {
async function fetchData() {
let data = await getPosts();
setPosts(data);
}
fetchData();
}, []);
return (
<div>
{posts.map((post) => (
<p key={post.title}>{post.title}</p>
))}
</div>
);
};
export default Home;
It's important to note that fetching data this way means that your page will render before the data is available. To avoid an error where your page is trying to render data it doesn't yet have, we set useState's initial value to an empty array. You can also create a loading state and show either a loading message or a spinner.
Wrapping Up
I hope you found these three different ways to fetch Contentful data useful. Basically, useStaticProps is a good option if you want a fast website and don't mind your data not being 100% up to date at all times. If you want to prioritize showing your most recent data and don't mind sacrificing a bit of speed, use useServerSide props instead. Finally, if neither of the options above suits your needs, you can fetch your data with useEffect on the client side.
Happy coding!