Skip to content
← Blog
bedrock — a security-first personal platform by Shawn Tjai

Hello, world

Shawn Tjai · · 3 min read

This is the first post on shawntjai.com, served from a static Astro build deployed to Cloudflare Pages. There is no database, no server runtime, and no third-party CMS phoning home in the background — just pre-rendered HTML, a strict Content-Security-Policy, and a deploy pipeline I actually trust. This post is about why I built it that way.

Why build it at all

I’ve shipped a lot of software over the last decade, and the personal site is usually the thing that gets neglected. It’s a single page, then a stale single page, then an embarrassing single page you quietly take offline. I wanted the opposite: a small but real platform I’d be happy to point a security-minded engineer at, and one I could keep extending without it turning into a maintenance burden.

So I treated it like a product. It has a spec, a plan, and a CI gate that refuses to merge anything that regresses accessibility, performance, or the security headers. The codename is bedrock — the foundation everything else sits on.

The principles

A few non-negotiables shaped almost every decision:

  • Security-first, not security-eventually. The CSP is default-src 'self' with no unsafe-inline and no unsafe-eval on the global policy. Every relaxation is scoped to a single path, documented inline, and tested.
  • Self-hosted everything. Fonts are vendored (no Google Fonts CDN), images are optimized and served from the same origin, and there are no third-party scripts on the main site. The fewer origins the browser has to trust, the smaller the attack surface.
  • No CLS, no jank. Every image ships with explicit width and height so the layout never shifts as assets load. Motion respects prefers-reduced-motion from day one.
  • Reproducible builds. Dates are formatted in a fixed locale and time zone so the static output is byte-stable across machines. CI is SHA-pinned.

How it’s wired

The stack is deliberately boring, which is a compliment. Astro’s content layer handles the blog and projects collections with typed frontmatter. A small remark plugin computes reading time at build. Open Graph cards are generated at build time so every post has a branded share image without me touching a design tool. The sitemap and RSS feed fall out of the content collections automatically.

The interesting part isn’t any single piece — it’s that the whole thing is held together by a verification gate. Lint, type-check, unit tests, accessibility audits with axe, and a Lighthouse budget all run before anything ships. If reading time breaks, if an image loses its dimensions, if the CSP drifts, the build goes red.

What’s next

This blog is the first place I’ll write about the parts worth sharing: the infrastructure decisions, the security posture, and the occasional mistake I learned something from. If you’re the kind of person who reads a personal site’s source before its prose, the repo is the real README. Welcome to bedrock.