Signals in practice
- Signals replaced most of the local RxJS, but observables still earn their keep at the HTTP boundary.
- computed() handles derivations cleanly as long as you keep side effects out of it.

Personal portfolio site I keep rebuilding to test new patterns. Screenshots here are from the original React build; the live site has been through several rewrites since.
A personal site that doubles as a sandbox for whatever framework or pattern I want to try next. The visible screenshots are from the very first build; the current version is a full Angular 18 rewrite with server rendering, structured data, and a self-contained contact API. The shape of the site has stayed roughly the same across rebuilds — what changes each time is the architecture underneath.
Every static and per-project route is prerendered at build time, with a Vercel SSR fallback for anything dynamic. Crawlers and link previews see real HTML and per-page metadata instead of an empty SPA shell.
A custom TitleStrategy reads route data and writes Person, WebSite, BreadcrumbList, and per-project CreativeWork JSON-LD into the document head. One source of truth for titles, OG tags, canonical URLs, and schema.
No NgModules. Signals drive component state, computed selectors handle derivations, and a small BrowserPlatformService wraps every browser API so the same code path renders on the server without crashing.
Contact form submissions hit a Vercel function gated by an HMAC-signed form token, a hidden honeypot field, and a per-IP rate limiter. No third-party captcha, no extra config beyond the SMTP key.
Most of the app was written assuming the browser was always available. Adding server rendering meant tracking down every direct touch of window, document, and storage and routing it through a single platform service so the same components could render on the server without crashing or producing hydration mismatches.
Page-level meta started out duplicated across four ngOnInit handlers, with quietly broken og:url and orphan OG image references. Moving everything into route data plus a TitleStrategy was straightforward; making sure server-rendered HTML and client navigations produced identical tags took more iteration.
The earlier math captcha was theatre — bots could call the email endpoint directly. Replacing it with an HMAC-signed token plus a honeypot keeps protection inside the codebase, avoids vendor lock-in, and stays invisible to real users.