This website
A portfolio built as evidence: a front end, an API, a database and monitoring, all running and all visible.
- My role
- Design and full-stack build
- Status
- Live, with a public status page
- Built with
- Next.js, React, TypeScript, three.js, Fastify, PostgreSQL, Drizzle, OpenTelemetry
- Who it's for
- Hiring managers and engineers who want to see how it is built
Why it needed to exist
A portfolio that only describes engineering asks the reader to take it on trust. I wanted the site itself to be the evidence, so that the parts a reader might ask about can be opened and checked.
Hiring managers and engineers who want to see how it is built, and anyone who wants to send a message.
What it does
- A contact inboxA validated form posts to the API. It is rate-limited per IP and screened with a honeypot and a spam score. What passes lands in an inbox on the admin page, so nothing is emailed and nothing can be lost in transit.
- Notes I choose to showI send a private, one-time link to people I have worked with. They leave a note with their name already filled in. It is stored as new, and I decide from the admin page which notes appear on the site, and can hide or show any of them later.
- A carousel that grows with themThe notes drift across in one row, and from eight notes in two rows moving in opposite directions. More notes fill the rows without any change to the page, and it stops moving for anyone who prefers reduced motion.
- A public APIRead-only endpoints for projects and experience are generated from the same content package as the site, with an OpenAPI document.
- A live status pageUptime, median and p99 latency, error rate and database health, read from the running API.
- A scene that stands downThe particle field loads after first paint, scales to the device, and is skipped where there is no usable GPU. Every page works without it.
- Tested end to endUnit, contract and integration tests against a real Postgres run on every pull request. Browser tests across engines, a load test and Lighthouse budgets run every week.
What I did
I designed and built all of it: the pages and the scene, the API and its schema, the tests and the deployment.
How it works
Browser
Next.js pagesStatic pages with a WebGL scene that loads after first paint.
Web
Next.js on VercelServes the pages and refreshes the notes when I show or hide one.
API
Fastify and ZodOne schema per endpoint drives validation, responses and the OpenAPI document.
Data
Postgres on NeonNotes, messages, rate-limit counters and request timings, with migrations kept in the repository.
Telemetry
OpenTelemetryTraces across web and API, and the figures behind the status page.
Status
Loading…
Choices that shaped it
- One typed content package
- The pages and the public API read the same validated data, so they cannot disagree with each other.
- I choose what shows
- Nothing is on the site until I have read it and chosen it. No quote is ever generated, and there are no placeholders.
- One-time invite links, not an open form
- An open form invites spam and leaves a reader wondering who wrote a note. A private link works once, expires, and carries the name it was made for. Only a hash of its code is stored, so a link cannot be read back from the database.
- An inbox, not email or a queue
- Messages arrive rarely, so a database table I read on the admin page is enough. There is no mail service to fail and no queue to keep alive.
- Rate limits and metrics in the database
- They live in Postgres, so they hold across every instance of the API and there is no cache server to run. Addresses are stored only as a keyed hash.
- Drop spam silently
- A filled honeypot or a high spam score gets the same answer as a real message, so a bot learns nothing from it.
- Keep Zod out of the browser
- Content is plain typed data checked by tests, which removed about 96 KB of gzipped JavaScript from every page.
- Measure real requests
- The latency and error figures come from live traffic in a rolling window, with monitoring and docs requests left out so they do not flatter the numbers.
- Stand the scene down without a GPU
- A software renderer cannot draw the field at a usable frame rate, and skipping it also skips the three.js download.
Where it stands
It runs, and the figures above are read from the running API rather than written down here.
Next: A chat that answers questions about my work from this site's own content; Privacy-first analytics.