An age-gated e-commerce & digital-delivery platform, production-ready in seven weeks.
Built by the one-man factory for an independent European content creator in an age-restricted (18+) segment. The client stays anonymous by policy — the engineering doesn’t have to. Every number below was measured against the shipped main branch on 15 July 2026, with the method stated per figure. Nothing is estimated; what couldn’t be measured reproducibly was left out, and it says so where it was.
A shop that hands over digital goods — safely, without accounts.
A production, bilingual (German/English) direct-to-consumer webshop and digital-delivery platform: product catalogue, cart and configurable orders, an age-verification gate before first paint — and the core: a token-authorised delivery portal where a buyer accesses their digital product through a one-time link, no account required. On top sits an administrative layer for manual access grants and an honest evidence trail of when access was granted and first opened.
Choices, and the trade-offs behind them.
| Layer | Choice | Why (the trade-off) |
|---|---|---|
| Frontend / runtime | Next.js 15 (App Router), React 19, TypeScript 5.8 strict | Server components by default — secrets and data access stay on the server and never enter the browser bundle. TypeScript strict is used as a contract enforcer, not decoration (see the decisions below). |
| i18n | next-intl 4 (de/en) | A fully localised route tree rather than runtime string swapping — SEO-correct localised URLs. The delivery portal deliberately sits outside the localised tree, locking language via an explicit, non-secret query parameter, so a future e-mail link can be constructed deterministically without changing the route. |
| Data layer | Postgres via Supabase (SSR cookie refresh) | Row-Level Security default-deny on every table; reads are anon-tier and tenant-scoped; privileged write paths run server-side only. Schema truth lives in 26 versioned SQL migrations, not ad-hoc changes. |
| Serverless | 2 Supabase Edge Functions | Payment/mapping logic isolated in a separate runtime with its own bounded auth — the shop’s Next runtime never touches that service-role boundary directly. |
| Observability | Sentry 10 | Error events are sanitised at the source — error code and message only, never raw PII or tokens. |
| Hosting / deploy | Vercel (app) + Supabase (DB/functions); GitHub as source of truth | Preview deploys per PR; staging kept in sync with main by automation. No direct pushes to main — ever. |
The architectural spine: raw access tokens are never stored — only their sha256 hash. The token is the sole authority (never an internal UUID), and it never crosses the server/client boundary.
Every change travels the same road. No shortcuts — not even for the owner.
- Design before code. Non-trivial features start as a ratified design document — the repo holds 147 markdown documents — reviewed before a line of code is written.
- Bounded pull requests. Everything lands via PR; main accepts only squash merges. Measured: 104 of 104 commits on main are merges carrying a PR number — the entire history is reviewed, nothing bypassed it.
- CI gates that block merge — 7 workflows: full-repo typecheck, the Vitest suite and a build smoke on every PR; SAST (Semgrep); secret scanning (TruffleHog) that blocks if a key leaks in; a deterministic staging-promotion gate; automated dependency updates with auto-merge only for verified bumps; and an automated review layer commenting on every PR.
- Test coverage in breadth: 72 dedicated test files, 30 of them security tests — RLS denial, service-role containment, token hashing, ingress guards. The suite runs ~880 tests across Vitest, Testing Library and Playwright browser flows.
The ones a senior engineer would nod at.
1 · Token-as-authority, hash-only storage, and a boundary the compiler enforces.
The access token is stored only as a sha256 hash and resolved in one place, once per request. It must never reach the browser — and that isn’t an intention, it’s a compile-time contract: a test asserts the component exposes no token prop, so the typecheck fails if anyone reintroduces it. The first-open transition is race-safe via a conditional UPDATE (WHERE status='issued' AND last_opened_at IS NULL) — two concurrent requests cannot both “win” the first opening.
2 · Security-by-default, enforced by tests — not by memory.
RLS is default-deny. The privileged service-role client may only be constructed in a single module — and a containment regression test fails if anyone constructs it elsewhere. Thirty security tests continuously confirm that anonymous and authenticated reads are denied where they should be. Security is a property CI can prove, not a note in a wiki.
3 · A verification regime that doesn’t trust green unit tests alone.
Customer-facing UI changes require reproducible browser evidence — Playwright, mobile and desktop, both languages — before merge, because a green unit test doesn’t prove a page actually renders. The same scepticism applies everywhere: in one case the full-repo typecheck caught a contract break that both the test suite and the build had missed. The discipline is “several independent axes must be green” — never “one green = done.”
Measured, not marketed.
| Metric | Value | How it was measured |
|---|---|---|
| Development period | 27 May → 15 July 2026 (~7 weeks) | First/latest commit date on main |
| Commits / merged PRs on main | 104 — all squash merges | git rev-list --count; PR numbers verified in every merge subject |
| Application modules | 92 TypeScript modules (excl. tests) | File tree listing against main |
| Test files | 72 — of which 30 security | Test-file glob + security suite directory |
| Database migrations | 26 versioned SQL | Migrations directory against main |
| Edge functions | 2 | Functions directory against main |
| CI workflows | 7 | Workflows directory against main |
| Design & ops documents | 147 markdown | Docs directory against main |
Deliberately left out: the total number of PRs ever opened (including closed/superseded ones) — it can’t be reproducibly derived from git alone, so only the 104 that landed on main are claimed. A precise “green test count” is not quoted as an absolute either: a handful of timeout-sensitive tests can flake under full parallel runs (green in isolation), so the stable, static measurement — 72 test files — is the number that gets cited. That’s what “with receipts” means in practice.
One thing deliberately not built.
Payment and dispute automation — not yet, on purpose.
The foundation exists: webhook ingestion and transaction mapping sit ready in an isolated runtime. But no live payment gateway is wired into production, and the fully designed “evidence pack” for defending a dispute is designed, not implemented — precisely because the system cannot yet mechanically prove a payment. The principle is reality-first: the platform only promises what it can trace to a concrete source. Persistent immutable evidence storage was likewise deferred in favour of an on-demand model — the least irreversible surface first. It’s boring to sell. It’s also how you avoid building a very precise machine for overclaiming.
This is one product from the factory.
Seven weeks, one operator, a fleet of AI agents — and an enforcement layer that keeps the speed honest. See how the whole thing works, or talk to me about what disciplined AI delivery would look like in your organisation.