Case Study
davblu.dev
The site you are reading: a portfolio and resume where every number has a source, from an empty repository to production in six days.
- Period
- – present
- Role
- Sole engineer
30
Issues closed in DavBlu Tasks
29
Automated tests
147
Commits
26–29 Sep 2026
First commit to résumé PDF

A portfolio where every number points at its source, and a publish goes live without a redeploy
Problem
A personal site usually asks the reader to take it on trust: years of experience, skills in a list, adjectives in place of evidence. I wanted one where every figure can be traced to a repository, a release or a live page, that I can keep current without touching code, and that runs on the VPS I already operate.
What was built
A Next.js App Router site with Payload CMS 3 embedded in the same app and SQLite on one volume, shipped as a single container to my VPS by GitLab CI, Docker Compose and a shared Traefik behind Cloudflare. Everything a reader sees (Case Studies, Journey, Skills, Hero, Contacts) is edited in the Payload admin with drafts and live preview. A publish expires the site cache, so the change shows on the next request, and the resume PDF at /resume.pdf is rendered from the same content.
Each Case Study and Position plays as a Scene with its own palette, backdrop and emblem, on the Showcase at /work, the Journey and a Skill Tree that grows with the scroll. A REST API with an API key lets each of my project repositories publish its own Case Study.
The hard parts were the evidence rules and the SQLite path. A Proof is shown only when it has a value worth showing: an absent, zero or failing one is hidden, never rendered as "0" or "down". The Activity Proof counts my commits across private repositories on my own GitLab through its API and publishes only the number. Payload's SQLite adapter runs on drizzle-kit, which dropped foreign key actions and failed some table rebuilds, so I patched it, commit every generated migration, and let the server apply pending migrations itself at start.
Outcome
Live at davblu.dev, deployed on every push to main after a pipeline runs lint, typecheck and tests. The same content produces the pages, Open Graph images, JSON-LD, the sitemap and the resume PDF.
From an empty repository to production in six days and 199 commits, from 26 September to 1 October 2026, tracked as Issues in DavBlu Tasks, with its decisions recorded as ADRs.
Shipping Timeline
From zero to production.
01 · Architecture
Stack chosen: Next.js, Payload CMS 3 and SQLite in one container
One process serves the site, the admin and the API; Case Studies, Positions, Skills and Proofs modelled as content (ADR-0001).
02 · Deploy
Deploy pipeline to the VPS
Container, encrypted secrets, GitLab CI deploy, Cloudflare DNS and TLS.
03 · Build
Drafts, access control and first pages
Drafts, live preview and access control in the admin; Case Study pages, Journey, Skill map and Shipping Timeline.
04 · Growth
Activity Proof, SEO and resume PDF
Commit count from my GitLab, metadata, Open Graph images, JSON-LD, analytics, /resume.pdf.
05 · Build
Scenes: Showcase, Journey and Skill Tree
Each Case Study and Position gets its own palette, backdrop and emblem, played with the scroll (ADR-0003).
06 · Support
Publishing API
An API key lets each project repository publish its own Case Study.
Engineering
How it is built.
Stack
- TypeScript
- React
- Next.js
- Tailwind CSS
- Node.js
- Payload CMS
- SQLite
- Docker
- GitLab CI/CD
- Traefik
- Encrypted secrets (SOPS, dotenvx)
- Vitest
Decisions
- Payload inside Next.js, on SQLite, in one container
- One editor and a read-mostly site on a VPS with no shared database. Embedding Payload gives the admin, authentication, drafts, live preview and uploads without writing them, and SQLite on one volume keeps the whole site to one container. Moving to Postgres later is a config change plus a data export (ADR-0001).
- One page for every Reader
- I first built a Lens switcher that rewrote the copy for recruiters, engineers and founders, then removed it: three copies tripled the writing, and every reader wants proof of shipping first. The page now gets deeper as you scroll instead (ADR-0002).
- Proofs are hidden, never shown as zero
- A missing, zero or failing Proof disappears instead of rendering "0" or "down", and a commit count not refreshed for seven days is withdrawn. Templates never hold numbers: every figure comes from a Proof, a Journey outcome or a Case Study metric.
- Migrations committed and applied by the server
- Production never pushes schema. Generated migrations are committed with each model change and applied when the server starts; a failed one stops the container rather than serving a half-migrated database. drizzle-kit is patched so SQLite keeps its foreign keys' ON DELETE actions.
- Secrets encrypted in the repository
- dotenvx encrypts .env.production in git, so a secret is added or rotated with a commit. The only private key lives in one masked, protected CI variable scoped to production.

