Creating dynamic, content-rich applications can seem complex, but with Contentful, React, and GraphQL, the process becomes much smoother. Contentful, a popular headless CMS, empowers you to manage and structure content independently of your app’s codebase. This makes it a fantastic choice for building apps that need frequent updates or support multiple content types without constant code tweaks.
In this post, we’ll walk through how to integrate Contentful with a React app using GraphQL. We’ll start by exploring Contentful’s advantages, from flexible content modeling to efficient content management. Then, we’ll dive into setting up Contentful models with migration files, which allow you to manage your content structure in a more scalable way. You’ll learn how to generate TypeScript types from Contentful's GraphQL API to keep your code robust and reliable. Finally, we’ll cover fetching data with Apollo Client to pull it all together, creating a seamless flow between Contentful and your React components.
By the end of this guide, you’ll have a solid foundation for using Contentful, React, and GraphQL together to power your projects with dynamic content. Let’s get started!
Contentful modeling
Contentful modeling focuses on defining content structures using "content types" that act like templates, each with customizable fields such as text, media, or references to other content types. Unlike traditional database modeling, which involves designing tables, relations, and queries, Contentful abstracts these complexities, letting developers manage relationships and hierarchies directly through its UI or APIs. This approach makes Contentful more intuitive for content creators while offering developers flexibility and scalability similar to database schema design.
Create a Contentful model with migration
The first step is to create a free account and create a CMA token in Contentful. You need this token to run migrations in Contentful.
Then you can create your React project with migration files. The next commands will create an empty project with the dependencies and file structure:
npm create vite@latest contentful-react -- --template react-ts && cd contentful-react && npm i && npm i -D dotenv ts-node contentful-migration && mkdir migrations && touch .env migrations/create_model.ts migrations/index.ts
The index file in the migrations folder is the entrypoint to run your migrations, and you can setup the migration connection here.
import "dotenv/config";
import * as path from "path";
import { runMigration } from "contentful-migration";
const options = {
filePath: path.resolve("migrations", "create_model.ts"),
spaceId: process.env.CONTENTFUL_SPACE_ID,
accessToken: process.env.CMA_TOKEN,
};
runMigration(options)
.then(() => console.log("Migration Done!"))
.catch((e) => console.error(e));
The config pointing to the first migration (create_model.ts), it will create a "Dog" model with two fields:
- Name: this is a simple text field
- Image: is an asset field, we can upload easily image to the entries.
import { MigrationFunction } from "contentful-migration";
module.exports = function (migration) {
const dog = migration.createContentType("dog", {
name: "Dog",
});
dog.createField("name").name("Name").type("Symbol").required(true);
dog.createField("image").name("Image").type("Link").linkType("Asset");
} as MigrationFunction;
That's it, you can run your migration with ts-node:
ts-node migrations/index.ts
Add some data to Contentful
After the migration, you are able to add some data on Contentful UI, under the Content tab.
If you click on the "Add entry" button, then you can enter the dog's name and you can upload an image. Your entry will be saved automaticaly in draft mode. You can publish it later with the "Publish" button.
We would like to fetch the content, so you need an API key. You can generate one under the settings.
Contentful provide you a graphql playgrond, where you can retrieve your contents. You have to replace the space id and api key in the url.
https://graphql.contentful.com/content/v1/spaces/{SPACE_ID}/environments/master/explore?access_token={API_KEY}
In the playground, if you run the the next query, then you will get the new entries in json format.
query getDogs {
dogCollection {
items {
name
image {
url
}
}
}
}
Generate Typescript types from the model
You can easily generate Typescript types from graphql queries, so it is much easier and safe fetch and use your content data in your app.
First, you have to install few packages:
npm i -D @graphql-codegen/cli @graphql-codegen/typescript-react-apollo
Then you need to create a "codegen.ts" at the root of your app. It is a config file for the code generation.
import "dotenv/config";
import { CodegenConfig } from "@graphql-codegen/cli";
const config: CodegenConfig = {
schema: [
{
[`https://graphql.contentful.com/content/v1/spaces/${process.env.VITE_CONTENTFUL_SPACE_ID}`]: {
headers: {
Authorization: `Bearer ${process.env.VITE_CONTENTFUL_API_KEY}`,
},
},
},
],
documents: ["src/**/*.graphql"],
ignoreNoDocuments: true,
generates: {
"./src/gql/graphql.ts": {
plugins: [
"typescript",
"typescript-operations",
"typescript-react-apollo",
],
config: {
withHooks: true,
},
},
},
};
export default config;
Now you can create a "dog.graphql" file in your src folder, it should contain the query what you tried in the playground. Codegen will create the types based on this query.
If you run now the next command, you will get all the types you need to fetch your content.
npm run graphql-codegen
Fetch data from Contentful with Apollo Client
The final piece of the puzzle is to fetch and use the content in your app.
Apollo Client is a great choice to handle graphql calls in your app.
There is the dependencies for Apollo:
npm i @apollo/client graphql
In main.ts, you have to setup Apollo Provider, then you can use Apollo anywhere in your app:
import React from "react";
import ReactDOM from "react-dom/client";
import { ApolloClient, InMemoryCache, ApolloProvider } from "@apollo/client";
import App from "./App.tsx";
import "./index.css";
const client = new ApolloClient({
uri: `https://graphql.contentful.com/content/v1/spaces/${process.env.VITE_CONTENTFUL_SPACE_ID}`,
headers: {
Authorization: `Bearer ${import.meta.env.VITE_CONTENTFUL_API_KEY}`,
},
cache: new InMemoryCache(),
});
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<ApolloProvider client={client}>
<App />
</ApolloProvider>
</React.StrictMode>
);
And finaly there is a component where we use the generated "useGetDogsQuery" hook. It give you a data object which contains all your content queried by graphql.
import "./App.css";
import { useGetDogsQuery } from "./gql/graphql";
function App() {
const { loading, error, data } = useGetDogsQuery();
console.log({ loading, error, data });
if (loading) {
return <>Loading...</>;
}
return (
<>
{data?.dogCollection?.items.map((dog) => (
<div className="card" style={{ width: "18rem", margin: "10px" }}>
<img className="card-img-top" src={dog?.image?.url || ""} alt="dog" />
<div className="card-body">
<h5 className="card-title">{dog?.name}</h5>
</div>
</div>
))}
</>
);
}
export default App;
If you run your app, you will see a list of dogs which includes the names and images of the dogs.
Finally, you have everything you need to build an app that’s both dynamic and easy to manage. I hope this guide helps you kickstart your project.
Feel free to share your thoughts or questions in the comments.
Happy coding!
Top comments (0)