
Deux Headless CMS
A hands-on exploration of building, connecting, and deploying a scalable Contentful–Next.js environment, combining design systems thinking with front-end engineering principles.
Deux Machina is a digital platform designed to centralize UX research, web development, and design resources. I set out to create an accessible, self-maintaining system where content editors could easily manage resources without developer input. My goal was to combine engineering precision with a designer’s attention to usability, using modern technologies—Next.js for performance and Contentful CMS for scalability.
1. Context & Objective
“I wanted to learn how to make the invisible architecture as intentional as the visible interface.”

I approached this project with a clear intent: to understand the architectural logic behind scalable design systems — from CMS setup to front-end integration and deployment. Rather than simply building an interface, my goal was to experience the complete product lifecycle as both a designer and engineer.
In practice, this meant architecting a small but fully functional system:
- Headless CMS (Contentful) for content management
- Next.js for static and dynamic rendering
- Vercel for automated build and deployment
My secondary goal was to identify where design and engineering intersect — understanding how data structure, API logic, and UI components influence the maintainability of a system.
.png)
2. Process & Technical Implementation
Step 1 — Fetching Data from Contentful
I began by setting up Contentful’s Content Delivery API through the createClient function.
A key learning here was securing environment variables to avoid exposure — I configured .env.local to store sensitive keys and established the foundation for safe API communication.
// Create a Contentful client and securely connect to the CMS
import { createClient } from "contentful";
export async function getStaticProps() {
const client = createClient({
space: process.env.CONTENTFUL_SPACE_ID, // stored securely
accessToken: process.env.CONTENTFUL_ACCESS_KEY, // environment variable
});
}
🧩 System integrity starts with clean configuration — this setup made every later step predictable and secure.
Step 2 — Building Dynamic Resource Components
To visualize CMS data, I created a ResourceCard component — modular, testable, and reusable. Initially, a missing null check caused render crashes; handling undefined data made the component fault-tolerant
export default function Resources({ resources }) {
return (
<div className="resource-list">
{resources.map(resource => (
<ResourceCard key={resource.sys.id} resource={resource} />
))}
</div>
);
}🧩 Small architectural safeguards — like conditional rendering — scale better than defensive patches later.

Step 3 — Dynamic Routing and Rich Content
Next, I implemented dynamic Static Site Generation (SSG) using getStaticPaths and getStaticProps. The learning curve was steep: forgetting to export getStaticPaths initially caused silent build failures. Fixing this taught me the precise mechanics of how Next.js maps slugs to pre-rendered routes.
export default function ResourceDetails({ resource }) {
const { featuredImage, title, description, tags, resourceUrl } = resource.fields;
return (
<div className="resource-details">
<img src={"https:" + featuredImage.fields.file.url} alt={title} />
<h1>{title}</h1>
<p>{tags.join(", ")}</p>
{documentToReactComponents(description)} {/* rich text renderer */}
<a href={resourceUrl} target="_blank" rel="noopener noreferrer">
View Resource
</a>
</div>
);
}🧩 “Dynamic systems don’t fail — they teach you the boundaries of your assumptions.”

Step 4 — Enabling Incremental Static Regeneration (ISR)
Finally, I enabled ISR by adding a revalidate parameter inside getStaticProps().
This allowed content updates to automatically rebuild without a full redeployment — turning a static site into a living, scalable system.
// Revalidates every 10 seconds to keep content fresh
export async function getStaticProps({ params }) {
const { items } = await client.getEntries({
content_type: "resource",
"fields.slug": params.slug,
});
return {
props: { resource: items[0] },
revalidate: 10, // ✅ Vercel auto-triggers rebuild every 10s
};
}🧩 Automation is the ultimate form of design efficiency — systems that update themselves scale effortlessly.

3. Reflection & Outcomes
“This project taught me that design maturity comes from understanding the systems behind the surface.”
The most valuable outcome of Deux Machina wasn’t just a working prototype — it was the shift in how I think about systems.
Every mistake — from export errors to data structure misalignment — forced me to refine my architectural logic and understand how design decisions ripple through technical infrastructure.
By the end, I had:
- Built a fully functional Contentful–Next.js–Vercel workflow
- Implemented modular and fault-tolerant React components
- Automated updates through incremental static regeneration
- Validated the workflow through a usability test with one participant