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.

Role

Designer–Developer (Solo)

Disciplines:

Product Engineering

Front-End Development

1. Context & Objective

“I wanted to learn how to make the invisible architecture as intentional as the visible interface.”
Landing page of the deuxmachina.vercel.app

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.

The CMS structure ensures modularity and clear dependencies between content models. Resources are the primary content type, each assigned to a resource type (one-to-many) and categorized with tags (many-to-many) for better filtering. Articles provide long-form content and can reference multiple resources (many-to-many) while also using tags for organization. The Google Books API is optionally linked to resources classified as books, allowing dynamic content embedding.

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.
Then, I implemented a Resources component to map over all entries and pass them into ResourceCard. This represents an intermediate development stage that demonstrates successful environment setup and data fetching from Contentful CMS through the ResourceCard component.

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.”
FThe screenshot demonstrates the initial CMS field outputs within the resources/free-faces route, as an example. Fields are displayed using the getStaticPath function to create a slug page identifier and getStaticProps to fetch specific data for each page by filtering with the slug query.

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.
A screenshot of the first automatic re-deployment after the ISR implementation.

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

Gamifying Ecosia

50%

Short heading goes here

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

50%

Short heading goes here

Lorem ipsum dolor sit amet, consectetur adipiscing elit.