EngineeringEngineering note

SEO as Data Modelling: How DueClix Insights Is Built

A page whose <title> says one thing and whose sitemap says another is not a content problem. It is a data model with two sources of truth. This publication was built so that class of bug has nowhere to live.

Author

DueClix Engineering

Published

Reading time

3 min read

Search engines read a page through several documents at once: the HTML title and description, the canonical link, the Open Graph and Twitter tags, the structured data, the sitemap entry and, for a publication, the feed. Each of those is a claim about the same page. When they are maintained separately, they drift — a title gets edited in one place, a URL gains a trailing slash in another, a date is regenerated on deploy — and the page starts contradicting itself.

When we built DueClix Insights, we treated that as a data-modelling problem rather than an SEO checklist. An article is one typed object. Everything a crawler reads is derived from it. There is no second file to fall out of step.

One object per article

Each article lives in a single TypeScript file under src/content/insights/. The shape is enforced by the compiler, so a missing description or a misspelt category fails the build rather than shipping a thin page.

export const example: Article = {
  slug: "seo-as-data-modelling",
  title: "SEO as Data Modelling: How DueClix Insights Is Built",
  description: "…",        // meta description AND listing excerpt
  deck: "…",               // the visible standfirst — deliberately different
  category: "engineering", // the beat, and the URL of its index page
  type: "engineering-note",// the form; never a route
  author: "dueclix-engineering",
  publishedAt: "2026-09-17",
  status: "published",
  body: [ /* typed blocks */ ],
};

Publishing is two edits: add the file, and add one import to the registry. No component, sitemap, schema, navigation or metadata file is touched.

What is derived, and from where

Every surface a crawler reads, and its single source
SurfaceDerived from
<title>seoTitle if set, otherwise title
Meta description, OG and Twitter descriptiondescription
Canonical, og:url, sitemap <loc>, RSS link, JSON-LD @idOne articlePath() function
datePublished, dateModified, sitemap lastmodpublishedAt and updatedAt
Article JSON-LD authorThe authors map — an Organization, not an invented person
Reading timeA word count over the body blocks
Related articlesNamed relationships first, then shared category, then shared tags

The row that matters most is the third. Five separate outputs need the article's URL, and a trailing-slash disagreement between any two of them is a real canonicalisation problem. Routing all five through one function makes that disagreement impossible rather than unlikely.

Blocks, not MDX

The obvious choice for a Next.js publication is MDX. We did not use it. MDX brings a compiler, a remark and rehype plugin chain and a second rendering path — several dependencies to gain authoring syntax that prose does not need.

Instead the body is an array of typed blocks — paragraph, heading, list, quote, table, code and a restrained note — rendered by one server component. The publication added zero new dependencies, every article page is a server component, and when we measured at launch, an article page shipped the same JavaScript bundle as an existing service page: 726 KB across nine scripts.

Three inline tokens, parsed into React

Inside a paragraph, the syntax is exactly three tokens: bold, inline code and a link. They are parsed into React elements, never into an HTML string, so nothing reaches dangerouslySetInnerHTML and React escapes every character of article text.

Three guarantees we tested rather than assumed

Drafts leave no trace

Drafts are filtered out at the single point where the registry becomes the list of articles, so nothing downstream ever sees them. We verified that with a real draft probe: zero routes, zero sitemap rows, zero RSS items and zero occurrences of its title in any generated HTML.

Dates never come from the build

A static site rebuilds often. If datePublished were derived from build time, every deploy would tell crawlers every article had just changed. Dates are fixed strings in the article; dateModified moves only when someone sets updatedAt for a genuine revision.

Categories cannot shadow articles

Category indexes and articles share one URL depth: /insights/engineering/ and /insights/seo-as-data-modelling/. Next.js refuses two differently named dynamic segments side by side — we confirmed it is a hard build error — so a single route resolves the slug as a category first, then as an article.

That creates a quiet failure mode: an article given a category's slug would simply vanish. A collision guard runs when the module loads and fails the build if that ever happens, so the mistake surfaces on a developer's machine instead of as a missing page in production.

The general point

Most technical SEO defects are not about search engines at all. They are the ordinary consequence of storing one fact in several places. The fix is the same one a database designer would reach for: pick one source of truth, derive the rest, and make the invalid states fail loudly before they are deployed.

Written by

DueClix EngineeringEngineering team

The team that designs and builds DueClix systems. We write about the parts of the work that are worth writing down.

Have a process worth improving?

Let's build the system behind it. Tell us what the process is and where it breaks — the first conversation is about constraints, not technology.