Skip to main content
Notes — min read

Rebuilding Mark - shipping a prototype I never shipped.

Mark started as a Supabase prototype that sat untouched for a year. Here is why I rebuilt it from scratch on an open-source stack instead of patching it, and what that taught me about owning your infrastructure.

Image of Osama
Osama MVP Engineer
Mark - CTA widget management dashboard
Mark - CTA widget management dashboard

Mark is live at mark.usama.cc.

It does one quiet thing well: it manages the calls-to-action on your blog. You build a widget once - image, link, UTM params, position - and drop it on every post through Google Tag Manager, a script tag, or an iframe. Then it tracks impressions, clicks and CTR per widget so you can see which CTA actually earns its place.

The app is not the interesting part. The interesting part is that I had already built it once, and never shipped it.


The prototype that sat in a drawer

A year ago this was a project called blog-cta. Next.js, Supabase for auth and storage, shadcn for the UI. It worked. It demoed fine. And then it sat in a repo for a year because “working” and “shipped” are not the same thing, and the gap between them is exactly the part nobody enjoys.

When I came back to it, the obvious move was to patch the old version - wire up the missing pieces, fix the rough edges, push it. I did not do that.

Why I rebuilt instead of patched

I rebuilt it from scratch on the stack I now use for everything in the portfolio: Better-Auth instead of Supabase Auth, Prisma and Postgres instead of Supabase’s managed database, MinIO instead of Supabase storage. All self-hosted, in Docker, on one VPS.

That sounds like more work, and at the start it is. But patching the old app meant inheriting a year-old set of dependencies, a managed service with per-seat pricing creeping in the background, and a codebase shaped by decisions I no longer agreed with. Rebuilding meant the auth, the storage and the database all matched every other app I run, so a fix in one is a fix in all of them.

The rule I keep relearning: owning your infrastructure is slower on day one and faster on every day after.

The rebrand

blog-cta was a describe-what-it-does name, which is fine for a folder and bad for a product. It became Mark.

The name carries the metaphor. The signature element on the marketing site is a marker-stroke highlight - the olive sweep behind a phrase that makes it pop without shouting. The whole identity leans into that: olive on cream, Fraunces for the headlines, a calm editorial feel rather than another neon SaaS landing page. The <Mark> highlight component is the brand.

Renaming a live system is its own small project - the GitHub repo, the Docker containers, the database, the storage bucket, the nginx vhost, the deploy keys all carry the old name until you go and change every one of them. Worth doing once, properly, rather than living with blog-cta in your infra forever.

What the rebuild actually was

People think the work in a product like this is the widget builder. It is not. The widget builder was a day.

The work was everything around it: auth that survives a deploy, image uploads to object storage, three different embed methods each with a copy-paste guide, an analytics pipeline that counts impressions and clicks without slowing the host page, and a dashboard that makes all of it legible. That is the product. The CTA editor is just the doorway.


What I took from it

Three things stuck.

A working prototype is not an asset. It is a liability with potential. It costs you nothing only if you ship it or delete it; left in between, it quietly accrues stale dependencies and decision-debt.

The last twenty percent is the product. Auth, storage, deploy, analytics, polish - the unglamorous wiring is the difference between a demo and something a stranger can sign up for.

And owning the stack compounds. Every app I move onto the same open-source foundation makes the next one cheaper to build and the whole set cheaper to run.

Mark took a fraction of the time the original did, because the foundation was already there. That is the real return on rebuilding.

Go try it if you write a blog and have never once measured whether your call-to-action works.