Skip to content
Back to writing
Feb 20266 min readArchitecture · Process

Building a portfolio with production-grade discipline

The site is small. The discipline behind it is the same one I bring to banking channel systems that serve millions of customers.

Most developer portfolios are built in a weekend and forgotten. They start as a template, get a coat of paint, and sit there collecting dust. That is fine if the portfolio is just a formality, a link to drop into a job application.

I wanted this one to earn its place. It is the interview before the interview: someone who spends a minute on it should come away thinking this person builds things carefully. So I built it the way I build a system at work. Here is what that looked like.

Start with the constraints, not the stack

When I build channel services, the first question is never which framework. It is what the constraints are. A kiosk in a branch 1,000km away has different constraints to a phone in someone's hand. The technology follows from the constraints, not the other way around.

Here the constraints were plain: static content that loads fast anywhere, no ongoing server cost, and room to grow into a blog and more without a rewrite. That pointed at Next.js with static generation on Vercel. Static output means zero server compute per request, the CDN handles global delivery, and the App Router leaves the door open for dynamic pages later. The move from static site to something larger is incremental, not a rebuild.

Engineering principle
Technology choices should trace back to a constraint. If you cannot explain a choice by pointing at one, you are choosing on familiarity or fashion.

Performance is a target, not an afterthought

At work, response time is not aspirational. A self-service terminal that takes four seconds to answer a tap loses the customer standing in front of it. We set time budgets per service call and hold to them.

The same habit applies here. I decided what the site should cost before building it, then measured against that.

RenderingStatic prerender. Zero server compute per request.
JavaScriptAround 102KB on first load, almost all of it the React and Next framework baseline. Page code is a few KB.
FontsSelf-hosted through next/font. No layout shift, no external requests.
MotionHover states and scroll-tracked navigation only. Nothing that loops or fires on scroll.

The honest figure is the JavaScript. First load sits near 102KB, and almost all of it is the framework baseline rather than anything I wrote. I would rather state that plainly than quote a number that only counts the parts that flatter me.

If you would not ship a service without a latency target, do not ship a site without a performance target.

Design as a system

The visual design uses the token-based approach I would use for a component library. Every colour, type size, and spacing value is defined once and referenced everywhere. No magic numbers, no inline overrides.

Two decisions shaped it. First, the site is light, not the dark navy that most engineer portfolios default to. Standing out here meant going the other way. Second, colour is not decoration: it encodes a system layer. Six colours map to design, edge, messaging, application, data, and observability, and a tag or card only wears a colour because of the layer it belongs to.

The type carries the same intent. Space Grotesk for headings and the name, Inter for body text, and JetBrains Mono for the small technical markers: dates, metrics, section labels. Monospace is a deliberate signal that a piece of text is a measurement or a label, not prose.

Design decision
Text on any colour tint uses the darkest shade of the same family, never black or grey. That keeps the palette coherent and clears WCAG AA contrast on every pairing without a separate dark variant.

Accessibility is a requirement

This was not optional. The terminals I work on serve customers across every demographic in the country, so accessibility is a baseline, not a nicety. The site holds to the same line.

It targets WCAG 2.1 AA. That means semantic landmarks and a skip link, a visible focus ring on every interactive element, alt text and aria labels on the icon links, and contrast checked on each colour pairing. The only motion is hover feedback and the navigation marker that tracks scroll position, so reduced-motion needs almost nothing to respect.

Built to grow

The decision I care about most is designing for what the site becomes, not just what it is today. All the copy, experience entries, work cards, and posts live in typed data files. Components render from that data, so the words can change without touching a component, and a new post or case study is a data edit, not a rebuild.

None of that needs an architectural change to grow. The same App Router structure that serves these static pages will serve a longer blog or a product page when there is one to add.

Lesson from banking
In channel systems we design the orchestration layer with extension points for services that do not exist yet. I applied the same thinking here: the structure has room for new content types without disturbing what already works.

What it proves

The portfolio is not the point. Every engineer has one. What matters is whether it shows the discipline you would bring to a team: choices traced to constraints, a performance target you can defend, accessibility treated as a requirement, a design system rather than ad-hoc values, and a structure built to grow.

These are the same habits I apply to systems serving millions of customers. The scale is different. The discipline is the same.

Built with Next.js and Tailwind CSS, deployed on Vercel. If you work on similar problems, channel services, service orchestration, or resilience engineering, I would enjoy the conversation.

Featured case study
Self-service terminals at national scale