From a simple blog to a small publishing system

How can a simple blog based on MDX gradually evolved into a small publishing system pushed by requirements that popped during development

Software DevelopmentMDXTypeScriptArchitecture

When I started building this website, the idea was actually pretty simple.

I wanted somewhere to write.

That was more or less the entire requirement.

I wanted to be able to document things I was building, things I was learning, experiments, open-source projects, and occasionally things that had nothing to do with software at all.

I didn't want to start with a CMS, a complicated publishing platform, or some elaborate content management architecture.

I just wanted to write an article, put it somewhere in the project, and have the website render it.

So I started with MDX.

At the time, I didn't actually know much about MDX. I knew Markdown, obviously, but MDX was something I hadn't really worked with before.

It turned out to be a much more interesting format than I initially expected.

Starting with Markdown

The first version of the content system was intentionally boring.

An article was essentially a file containing frontmatter and Markdown:

md
---
title: "My Article"
description: "Something I wanted to write about."
date: "2026-08-17"
tags:
  - Engineering
---

# My Article

Some thoughts here.

The application could read the file, extract the metadata, and render the content.

That was enough.

And for a while, I didn't really need anything else.

But that's usually where these projects become interesting.

You solve the original problem, and then you start using the thing you built.

And once I started actually writing, I started noticing things I wanted to improve.

MDX Was the First Surprise

One of the first things I discovered was MDXComponents.

I hadn't really thought about Markdown as something that could have a rendering layer behind it.

I was used to thinking about Markdown as content.

MDX changed that perspective.

Instead of treating every element as something the Markdown renderer simply outputs, I could decide how certain elements should behave inside the application.

Links could have their own behavior.

Images could have their own implementation.

Blockquotes could be styled consistently.

And code blocks could become something much more interesting than a <pre> element.

The structure started becoming something like:

text
MDX

 ├── headings
 ├── paragraphs
 ├── links
 ├── images
 ├── blockquotes

 └── code

 MDXComponents

   CodeBlock

That was one of those moments where the project stopped being just a collection of Markdown files.

There was now a small rendering system behind the content.

Then I Wanted Better Code Blocks

This was probably inevitable.

If I'm going to write about software, I'm going to have code in the articles.

And plain code blocks were functional, but they didn't really feel like something I wanted to publish.

So I started looking into syntax highlighting and ended up using Shiki.

That gave the code blocks a proper syntax-highlighting layer while still letting me keep the source itself inside the MDX document.

But again, one feature led to another.

Once I had syntax highlighting, I wanted filenames.

Then I wanted a copy button.

Then I wanted to highlight specific lines.

Then I wanted to pass metadata through the Markdown itself.

That eventually led to the remarkCodeMeta plugin and a custom CodeBlock component.

Something as simple as:

ts
const gradient = DynamicGradient.init("#hero", {
  type: "linear",
  colors: ["#ff7e5f", "#feb47b"],
});

could now carry information that the rendering system could use.

I found that part particularly interesting.

The article itself could describe how the code should be presented without requiring me to manually build a React component every time I wanted to show something.

The content was beginning to contain not only the information being presented, but also some of the information about how it should be presented.

That distinction became important later.

Then the Scope Started Growing

This is probably the most predictable part of the whole project.

The website started as:

I want somewhere to write.

Then it became:

I want somewhere to write comfortably.

Then:

I want the articles to look good.

Then:

I want the code examples to look good.

And eventually:

I want to be able to publish without having to manually create files inside the repository.

That last one changed the direction of the project quite a bit.

Because technically, the system already worked.

I could create an .mdx file, commit it, deploy the application, and there would be a new article.

But there was something slightly strange about that workflow.

If the purpose of the project was to give me somewhere to write, why did writing something new require me to go into the codebase?

The content was being treated as part of the application source code.

That wasn't necessarily wrong.

For a developer blog, it actually makes a lot of sense.

But I started wondering whether I could keep the simplicity of the MDX-based system while eventually giving myself a proper interface for creating and managing content.

I didn't want to immediately solve that with a CMS.

I wanted to see how far I could take the system I had already built.

And that became the next problem to solve.

Then Came Portuguese

There was another requirement that I hadn't originally planned for.

I was writing everything in English.

But I wanted the site to work properly in Portuguese as well.

And I didn't want Portuguese to feel like a secondary version of the website.

The obvious solution would have been to reach for an existing internationalization solution.

Instead, I decided to make localization part of the content model itself.

The structure eventually became:

text
content/
├── blog/
│   └── building-dynamic-gradient/
│       ├── en.mdx
│       └── pt.mdx

└── notes/
    └── hello-world/
        ├── en.mdx
        └── pt.mdx

Now the locale wasn't just something the interface needed to know about.

It became part of the content.

That decision affected much more than the articles themselves.

It meant thinking about:

  • locale-aware routes
  • language switching
  • metadata
  • breadcrumbs
  • static generation
  • post lookup
  • RSS
  • navigation

A requirement that initially sounded like:

"I want an English and Portuguese version."

ended up influencing almost the entire application.

And this was another example of the architecture emerging from the requirements rather than being designed in isolation beforehand.

The Project Keeps Teaching Me What It Needs

This is probably what I've enjoyed most about building the site.

None of these things were part of some massive architecture plan I had from the beginning.

I didn't sit down and design a publishing system.

I wanted a blog.

Then I wanted better content rendering.

Then better code blocks.

Then localization.

Then a better publishing workflow.

And each requirement exposed another small problem worth solving.

The architecture has been growing from those requirements instead of trying to predict all of them upfront.

I think that's something I've increasingly appreciated about software development.

Sometimes the best architecture isn't the one you can imagine before writing the first line of code.

Sometimes it's the one that emerges after you've actually used the thing you're building.

There is a difference between designing for hypothetical requirements and designing in response to real ones.

The former can produce impressive diagrams.

The latter tends to produce software that actually reflects what the system needs.

And Now I'm Building the Thing That Builds the Thing

The funny part is that I'm not finished.

The next step is probably the most interesting one.

I want to be able to log into the site and create a post from the site itself.

Add the title.

Write the content.

Add images and links.

Choose whether it's a Blog post or a Note.

Select the language.

Preview it.

Publish it.

And ideally, never have to open the repository just to make an article exist.

Which means that the project that started as:

"I need somewhere to write."

is slowly becoming a small publishing system.

And I suppose there's something appropriately recursive about that.

I started building this site because I wanted somewhere to write. Now I'm building the tools that make it easier for me to write.

And, naturally, I'm writing about building those tools.