Deux Machina interface collage in a browser environment

Deux Headless CMS

Technical ExplorationNov 2025

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.
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.

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.

Contentful client setup
// 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,
    accessToken: process.env.CONTENTFUL_ACCESS_KEY,
  });
}
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.

Dynamic resource list
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.
An intermediate development stage showing environment setup and data fetching from Contentful through ResourceCard.

Step 3 - Dynamic Routing and Rich Content

Next, I implemented dynamic Static Site Generation using getStaticPaths and getStaticProps. 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.

Resource detail route
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)}
      <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.
CMS field outputs within a resource route, fetched by slug and rendered as rich content.

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.

Incremental Static Regeneration
// 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,
  };
}
Automation is the ultimate form of design efficiency - systems that update themselves scale effortlessly.
A screenshot of the first automatic redeployment 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.

  • 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