Mahdi
All projects

Personal site

A portfolio site usually forces a choice: a static site that a non-developer cannot update, or a CMS that adds a database, a vendor and a monthly bill. I wanted neither. The requirement was simple and slightly unusual — write content in the same editor I already use, and never lose a version of it.

TanStack StartReact 19TypeScriptCloudflare WorkersTailwind CSSshadcn/uiZodShiki
The design constraintThe site deploys to Cloudflare Workers, which has no filesystem. That single constraint decided the content architecture.The obvious approach is a database and a read at request time. The alternative used here is to bundle the content at build time with Vite's import.meta.glob and ?raw, then validate every file against a Zod schema. The result is one code path that behaves identically on Workers, Node and Vercel, and no database anywhere in the stack.The cost is real and worth stating: content only changes when you rebuild. For this site that is not a limitation, because every save is a git commit and a deploy is the build.Git is the databaseContent lives as Markdown files with YAML frontmatter, one file per post and per project. That choice is not about novelty — a database would have to reimplement everything git already does for free:version history and git diff on every changea real rollback (git revert) rather than a soft-delete flagreviewable content in pull requests before it goes livegreppable across every post written so farFrontmatter is validated, not trusted. Every field goes through a Zod schema, so a typo in a date or a missing summary fails the build with the file path and the offending field named, instead of rendering a broken page:
[content] invalid frontmatter in content/blog/example.md:
  - date: expected YYYY-MM-DD
  - summary: Required
The admin panel validates against the same schemas, so a bad write is rejected at the panel rather than discovered on the next deploy.Why a single-admin panel gets simple authThere is exactly one user, and the value of the site is the content, not the admin UI. A password from the environment, a signed cookie, and a server function that guards every mutating route.Pulling in an auth vendor for this would have added a dependency, a signup, a dashboard, and an outage surface in exchange for solving a problem that does not exist. The interesting work here is the content pipeline, not the login form.RenderingMarkdown is parsed at build time and highlighted with Shiki, which does the highlighting at build rather than shipping a highlighter to the browser. The output is static HTML with no client-side Markdown cost.What is deliberately absentNo database. Content is files.No client-side state library. TanStack Query handles the one thing that genuinely needs it, and nothing else was added.No CSS-in-JS. Tailwind plus a small set of tokens in styles.css.Each of those was available and each was left out. The interesting decisions in a build are the exclusions, and those are usually the ones nobody writes down.

Keep reading