Product

tonycletus.com

How tonycletus.com is built: Astro on Cloudflare Pages with a self-owned CMS, double opt-in newsletter, and first-party reading analytics.

August 2, 2026

Tagged: AstroCloudflare PagesPostgresEdge FunctionsResendTypeScript

Started
June 2026
Status
Live
Signal
Site and CMS

The question

How much publishing infrastructure can one person own without renting five SaaS tools?

The usual answer is a hosted site builder, a newsletter service, an analytics script and a form provider. Four subscriptions, four dashboards, and your reader list living somewhere you do not control. I wanted to see what it costs to build the same thing myself.

Context

The site started as a resume page and kept growing extra parts: projects, writing, an RSS feed, a subscribe box. Each new part came with a decision about whether to pay for it or build it. I built it, mostly because I wanted to know how the plumbing works, not because it was cheaper.

Building it

It is an Astro static site deployed on Cloudflare Pages. Content lives as Markdown in the repo, so a git push is a publish.

On top of that there is a small admin at /admin that reads and writes the same posts through a Postgres backend, so I can edit from a browser without opening an editor. Auth is role-gated: an admin role in a separate table, checked by a security-definer function, never a flag on a user row.

The newsletter is mine end to end. Subscribers sit in my own table with double opt-in, sending goes through Resend, and a slug ledger makes a send idempotent so a post can never go out twice. Publishing from admin fires the send, and a scheduled job acts as a safety net if the push happened outside the admin.

Analytics are first party and deliberately thin: a beacon on article pages records a view, reading time and whether the reader reached the end, plus a share counter. No third-party script, no cookie banner, because there is nothing to disclose.

What surprised me

The hard parts were not the features. They were the seams: a beacon that silently fails CORS if the payload is not a safelisted content type, an email confirmation link burned by a scanner before the human clicks it, a lockfile drifting out of sync and killing the build. Each one looked like a different bug and was really the same lesson, which is that integration points fail more often than logic does.

What I learned

Owning the list and the numbers changes how you write. When the reader data is yours, you stop optimising for a dashboard someone else designed and start asking whether anyone finished the piece.

← All products