JR Logo
Web Portfolio
Updated Apr 2026Angular 18

Web Portfolio

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.

Build Key Features

  1. 01

    Server-side rendering with prerendering

    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.

  2. 02

    Structured data via TitleStrategy

    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.

  3. 03

    Standalone signals throughout

    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.

  4. 04

    Self-contained contact API

    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.

Friction Challenges

  1. 1

    Making SSR genuinely safe

    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.

  2. 2

    One source of truth for metadata

    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.

  3. 3

    Spam protection without extra services

    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.

Takeaways Learnings

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.

SSR ergonomics

  • Most SSR pain comes from libraries that touch the DOM at module load — easier to wrap them than to fix them.
  • Prerendering covers nearly everything for a site this size; on-demand SSR is just a fallback for unmatched routes.

Trimming as you go

  • Each rewrite has been a chance to delete features that looked good in isolation but didn't earn their place.
  • A portfolio is a useful place to keep the bar high — it's small enough that the right answer is usually less code, not more.