Mapping Headless CMS Data to Your Frontend Like a Pro
(a.k.a. How to Stop Yelling at Your JSON)
You finally connected your frontend to your headless CMS. Congratulations! 🎉 Now comes the part that separates the rookies from the battle-hardened devs: mapping CMS data into something your frontend actually understands.
Because let’s face it — the data structure your CMS gives you and the one you want are rarely the same species. 🦧
🤯 The Typical Scenario
Your CMS sends this beauty:
{
"data": [
{
"id": 1,
"attributes": {
"title": "My First Blog",
"author_name": "Lucas",
"content": "<p>Hello world!</p>"
}
}
]
}
But your React component just wants:
{
id: 1,
title: "\"My First Blog\","
author: "Lucas",
content: "Hello world!"
}
The firs…
Mapping Headless CMS Data to Your Frontend Like a Pro
(a.k.a. How to Stop Yelling at Your JSON)
You finally connected your frontend to your headless CMS. Congratulations! 🎉 Now comes the part that separates the rookies from the battle-hardened devs: mapping CMS data into something your frontend actually understands.
Because let’s face it — the data structure your CMS gives you and the one you want are rarely the same species. 🦧
🤯 The Typical Scenario
Your CMS sends this beauty:
{
"data": [
{
"id": 1,
"attributes": {
"title": "My First Blog",
"author_name": "Lucas",
"content": "<p>Hello world!</p>"
}
}
]
}
But your React component just wants:
{
id: 1,
title: "\"My First Blog\","
author: "Lucas",
content: "Hello world!"
}
The first time I saw this, I said, “Surely there’s an easier way.” Then I mapped it manually for two hours and cried softly into my keyboard. 🧑💻💧
🛠️ Step 1: Normalize the Data
Here’s the golden rule: Don’t pass CMS data directly into your components. You want clean, predictable data.
function normalizePost(cmsPost) {
const { id, attributes } = cmsPost;
return {
id,
title: attributes.title,
author: attributes.author_name,
content: attributes.content.replace(/<\/?p>/g, ""), // optional cleanup
};
}
Then when you fetch:
const res = await fetch("https://cms.example.com/api/posts");
const json = await res.json();
const posts = json.data.map(normalizePost);
Boom. 💥 Now your frontend gets exactly what it wants.
🎯 Step 2: Create a Mapper Layer
If you work on large projects, create a separate folder for mapping/transforming data.
src/
┣ api/
┃ ┣ cms.js
┃ ┗ mapper.js
┗ components/
In mapper.js:
export const mapPost = (item) => ({
id: item.id,
title: item.attributes.title,
author: item.attributes.author_name,
content: item.attributes.content,
});
export const mapAuthor = (item) => ({
id: item.id,
name: item.attributes.name,
avatar: item.attributes.avatar?.url,
});
You get clean, maintainable data transformations, and your components stay lightweight and happy.
⚡ Step 3: Handle Missing or Weird Fields
Real talk: CMS editors will forget to fill something in. So instead of your app exploding, give it a hug:
export const mapPost = (item) => ({
id: item.id,
title: item.attributes.title || "Untitled Post",
author: item.attributes.author_name || "Anonymous",
content: item.attributes.content || "No content yet...",
});
Optional chaining helps too:
item?.attributes?.title ?? "Untitled"
🧠 Step 4: Version Your Mappers
CMS data structures change over time. You add a field. You rename something. You forget what you did. 😅
To keep your sanity:
- Add comments or JSDoc for each mapper
- Keep old mappers until migrations are done
- Write simple unit tests for your mappings (trust me, future-you will be grateful)
🧩 Step 5: Feed the Frontend
Now, when you render posts:
{posts.map(post => (
<PostCard key={post.id} title={post.title} author={post.author} content={post.content} />
))}
It’s clean. Predictable. Reliable. Like that one friend who always brings snacks to debugging sessions. 🍪
🚀 Bonus: Add TypeScript (If You Like Type Safety and Pain)
If you’re using TypeScript:
interface CMSPost {
id: number;
attributes: {
title: string;
author_name: string;
content: string;
};
}
interface Post {
id: number;
title: string;
author: string;
content: string;
}
export function mapPost(item: CMSPost): Post {
return {
id: item.id,
title: item.attributes.title,
author: item.attributes.author_name,
content: item.attributes.content,
};
}
Your IDE now catches 80% of your future headaches automatically. 🧘♂️
✍️ TL;DR
- Normalize your data. Never use raw CMS responses.
- Use a mapper layer. Keep logic out of components.
- Handle missing fields. Gracefully, not dramatically.
- Version your mappers. Change is inevitable.
- Consider TypeScript. Or don’t. Your choice.