The whole stack. One person.
Modern web apps with a CMS your team can actually use. Researched with the people who will use them, then designed, built, secured, hosted and run by me. Interface to DNS.
How I build
Minimal technical debt.
Every dependency, build step and moving part is a future cost to you. I only add one when it pays for itself, and I remove what stops paying. Code is linted, typed or validated at its edges, tested in CI, and documented well enough that someone else could take it over. Everything below follows from that.
Slim servers.
Small, single-purpose Node services with thin request handlers. No heavyweight runtime, no plugin pile. Several production apps share one 2GB VPS, and some have no npm dependencies at all. You get speed and a small hosting bill.
JSON throughout.
Content is structured data from a headless or JSON-backed CMS, not pages locked in a theme. Schemas are validated at the boundary with Zod or JSON Schema. The same data feeds your website, a PWA, a dashboard or another system without rework.
Built in the browser.
The server sends an app skeleton once, then JSON over a REST API. The logic runs on the client, which fills HTML templates and web components with that data. The server never assembles a page, so it does less work and costs less to run. Content goes in through the DOM. No innerHTML, no HTML built from strings, no server-side includes. The markup is semantic elements, not layers of divs hung with ids. No jQuery, no getElementById, no var. Search engines and link previews still get whole pages: on the chorus sites every public route is rendered once in a headless browser in CI and served as a static snapshot, and an edit in the CMS re-renders the pages it touches within a couple of minutes.
Build in CI or not at all.
Servers run code. They do not build it. Where a server needs TypeScript, Node runs it directly with type stripping.
Current versions, always.
Dependencies stay on current releases. They are not left to rot until an upgrade becomes a project.
Standard and portable.
Nothing proprietary. The same app runs on a VPS, on Vercel, on Cloud Run or on Azure, and you can take it elsewhere.
AI-assisted, engineer-owned.
I use AI tools to write and review code faster. That is how one person delivers at this pace. I read, test and answer for everything that ships.
The lot
Every layer, from the people using it down to the network. No subcontractors. No gaps.
-
Research and design
I find out what people need before I build it, then test it with them.
- User research
- Interviews
- Usability testing
- Prototyping
- Interaction design
- UX
- Accessibility to WCAG 2.2 AA
-
Research and data analysis
I design the study, run it, analyse the results and report them so a person can read them. Large datasets included.
- Study design
- Surveys
- Data analysis
- Large datasets
- Python
- R
- Bespoke analysis tools
- Dashboards
- Reporting
- Peer-reviewed publication
-
Architecture
Designed for the job and sized to run fast on modest hardware.
- Headless CMS
- REST and JSON APIs
- Small services
- App skeleton
- Client-side logic
- HTML templates
- Web components
-
Web apps with a CMS
Your staff edit the content. Nobody phones a developer.
- Payload
- Directus
- A custom CMS written for the job
-
Interface
Hand-written where that is enough, a framework where the app warrants it.
- HTML
- CSS
- JavaScript
- React
- Next.js
- TypeScript
- Tailwind
-
Application and data
APIs, databases, and the data you already have, moved in safely.
- Node
- Express
- Fastify
- REST and JSON APIs
- Postgres
- SQLite
- Prisma
- Full-text search
- Data migrations
- Legacy imports
- Feeds to and from other systems
-
Legacy applications
I know the older frameworks, and move applications from server state to true RESTful APIs.
- MVC
- PHP
- Laravel
- CodeIgniter
- Ruby on Rails
- JSP
-
Identity
One sign-in for every app, with roles that match how your organisation works.
- Passkeys (WebAuthn)
- OIDC
- Single sign-on
- Google sign-in
- Magic links
- Role hierarchies
- Central identity service
-
Payments and ticketing
- Stripe
- Box office
- Events
- Bookings
-
Integrations
- Social media hooks
- Webhooks
- Media pipelines
- Third-party feeds
-
AI
LLM features behind a provider you can swap, with privacy designed into the schema.
- LLM features
- Swappable provider interface
- Privacy by schema
-
DevSecOps
- GitHub Actions
- Automated deploys
- Dependency patching
- Content Security Policy
- Security headers
- Secrets management
- pm2
- Docker
- Monitoring
- Health checks
-
Hosting
Several production apps on one small box.
- Linux VPS
- nginx
- Vercel
- Neon
- Google Cloud
- Managed Postgres
- Backblaze B2
- Object storage behind a CDN
-
Cloudflare
- DNS
- CDN
- Caching
- TLS
- Edge protection
-
DNS and email
Your domain cannot be spoofed, and your mail arrives.
- DNS management
- Domain migrations
- SPF
- DKIM
- DMARC to p=reject
- BIMI
- MTA-STS
- Mail forwarding
- Mailing lists
- Transactional email
-
Home and business automation
Devices from different makers, working together on a small computer you own.
- Home Assistant
- Docker
- Zigbee
- MQTT
- 433 MHz radio
- Presence sensors
- Heating control
- Dashboards
-
Network
- Reverse proxies
- TLS
- Private networking
- VPN (Tailscale)
- Network design
-
Policy and governance
I write the policy as well as the code.
- Data protection by design
- Accessibility
- Security policy
- Safeguarding
- Regulatory compliance
Specialities
Six parts of the work, each with a page of its own.
-
Payload CMS
Payload CMS developer in the UK. I build and run production sites on Payload, including a charity's whole platform of five Node services on one server.
-
Directus
Directus developer in the UK. I build on Directus when the data comes first: a SQL database with a clean editing screen and a JSON API on top.
-
Email authentication
DMARC p=reject setup with SPF and DKIM aligned, without losing legitimate mail on the way. In production for a charity and for this domain.
-
Passkeys and single sign-on
Passkey login and single sign-on: one identity service for every app, with WebAuthn, OIDC and roles that match how the organisation works.
-
Legacy apps to REST APIs
Legacy app modernisation: moving PHP, Laravel, CodeIgniter, Ruby on Rails and JSP applications from server-held state to true RESTful APIs.
-
Home Assistant
Home Assistant setup and automation in Docker: Zigbee, MQTT and 433 MHz radio, presence lighting, heating, watchdogs and a nightly config backup.
Work
A charity platform, small-business sites on custom CMSs, university systems and a public archive.
-
Solent Gay Men's Chorus
Registered charity. The whole platform, built and run by me.
- One identity service for every app, with passkey sign-in and a role hierarchy.
- Stripe payments taken directly by the box office, so there is no third-party handling fee on top of Stripe's. Ticket sales and events.
- Client-rendered and still indexed. Every public route is snapshotted in headless Chromium on GitHub Actions and served by nginx as static HTML, so crawlers and link unfurlers that run no JavaScript get the whole page. The browser is never run on the server.
- A CMS edit re-renders the routes it touches and pings IndexNow; the snapshot lands in a minute or two. A full render twice a day is the backstop and prunes deleted pages.
- Per-page title, description, canonical, Open Graph tags and JSON-LD (Event with offers, Article), set on the client and captured in the snapshot. The sitemap is generated from the CMS, with images.
- A governance system built to manage becoming a CIO. I also wrote the policies and the new constitution.
- Media library on object storage behind Cloudflare.
- Social media hooks, member email and a CMS the committee edits itself.
- Five Node services: main website, box office, governance, members portal and identity. Content in Payload.
- All five share one 2GB server behind nginx, deployed from GitHub Actions.
- Email domain at DMARC p=reject, with DKIM aligned.
-
SUMS
Single-blind double-marking platform for a university, with automated reconciliation.
How it is built: SUMS
- Single-blind double marking with automated reconciliation, and allocation of supervisors and moderators to projects.
- Federated login through the institution's single sign-on: no local accounts to manage or to attack.
- In production since 2018 on Google Cloud, with no servers to run and a full audit trail.
- About 600 projects a year: 350 undergraduate and 250 master's, plus resits and two international partner programmes. About 100 markers a year.
- More than 4,800 projects marked since 2018.
-
Student feedback platform
Commissioned by a university, designed from research with students and staff. In testing.
How it is built: Student feedback platform
- Replaces Evasys, which had no direct links to surveys and no integration with other systems.
- Annual surveys and module pulse surveys.
- Next.js and TypeScript on Vercel, with Postgres on Neon and Prisma.
- Anonymity is enforced by the schema: a response cannot be joined to the student who wrote it.
- LLM features flag concerning sentiment immediately and aggregate common themes for staff and reps. They sit behind a swappable provider interface.
- In testing.
-
Survey analysis dashboard
National Student Survey analysis for a whole university. Adopted across a faculty. In use since July 2026.
How it is built: Survey analysis dashboard
- Running within three hours of the national results being published.
- Reads the Office for Students' National Student Survey workbooks and full-data extract directly.
- Rolls up institution, faculty, school and course, with year-on-year comparison when two years are loaded.
- Concern flags: five points below the institution's own figure, below 70% overall, or a fall of more than five points in a year.
- Nine years of tagged free-text comments (2018 to 2026) analysed deterministically for consistent, new and receding themes.
- A local model through Ollama handles the sentiment analysis and writes the per-unit narratives, so no cloud AI service saw student comments. Output is paraphrase and counts only, scanned for personal details.
- Node and vanilla JavaScript ES modules, with tokenised CSS. No framework.
- Google sign-in limited to the institution's domain, verified server-side, with a signed session cookie. Raw comments sit behind an allowlist of senior staff.
- The test suite recomputes every school and faculty mean and standard deviation independently, and renders every view in jsdom.
- nginx and pm2 on a VPS.
-
geraldlarner.com
A public, searchable archive, built from a pile of old files.
How it is built: geraldlarner.com
- About 6,700 legacy word-processor files: WordPerfect, RTF (including old Nisus RTF) and Word.
- A Python pipeline converts them with headless LibreOffice, with its own parser for the files LibreOffice cannot read, then classifies each as a work, an essay or source material.
- Manual corrections are keyed by file path, so a rebuild is repeatable.
- 5,388 notes on 3,522 works by 445 composers.
- A thin Express server over one SQLite file of pre-rendered pages, with full-text search and a JSON API.
-
alicedennis.net
Site for a piano and singing teacher. Replaced a broken legacy site.
How it is built: alicedennis.net
- A custom lightweight CMS with a built-in admin page and Google sign-in, so the owner edits it herself.
- One small Express 5 process. No database, no build step and no client framework: the content is one validated JSON file.
- Uploads are resized to WebP in the browser, so the server never decodes an image.
- Every page is server-rendered with its own title, description, canonical link and structured data. The old site's URLs get a 301 to the page that replaced them.
- Contact form: honeypot, signed timing cookie, rate limits, content scoring and blocklists. Suspect mail is flagged, not dropped.
- Deployed by GitHub Actions after lint and tests, through a key that can only run the deploy script.
- nginx and pm2 on my VPS.
-
morganharnett.tattoo
Site and booking system for a tattoo artist, on a custom CMS written for the job.
How it is built: morganharnett.tattoo
- It fills itself from Instagram, so the artist updates one place. The official Instagram API, synced every six hours in its own short-lived process.
- Booking flow: apply, offer, pick a slot, pay a deposit through Stripe Checkout. A slot is held for 35 minutes while the client pays.
- Availability is his working hours, minus his Apple Calendar over CalDAV, existing bookings and held slots.
- A Fastify API in TypeScript, run directly by Node with no server build step. State lives in one SQLite file.
- A React front end rendered on the server, so crawlers get real pages, then precached by a service worker as an installable PWA.
- Alt text for new images from Claude Haiku. The backlog was described locally with Ollama.
- Google sign-in for the admin. Deployed by GitHub Actions after tests.
-
Photo archive for a school
Private. Built on Directus.
How it is built: Photo archive for a school
- Artists' photographs managed in one place so students can learn from them. About 2,100 images.
- Directus on Postgres for the data and the admin, with a custom interface extension for editing photographs. Schema snapshots are committed.
- A Vite and React front end in TypeScript, served as static files by nginx.
- Images in S3-compatible object storage.
- The whole site sits behind Google sign-in through oauth2-proxy. The Directus API is same-origin and private.
-
ASICA
Research tablet app for melanoma self-monitoring. University of Aberdeen, published in BMJ Open in 2015.
How it is built: ASICA
- Designed with patients and run in a six-month NHS pilot.
- An app for the Google Nexus 7 tablet. Reports, with photographs taken on the tablet, went to a secure remote server for review by a clinical nurse specialist.
- Developed under the Medical Research Council framework for complex interventions. Behaviour change techniques are built into the flow: prompts, demonstration, checklists and feedback.
- Pilot in NHS Grampian: 20 patients, six general practitioners and one nurse specialist. 15 adhered well. Two had lesions excised, one of them a recurrent melanoma.
- Murchie P, et al. BMJ Open 2015;5:e007993. Funded by the RCUK Digital Economy programme through the dot.rural hub at the University of Aberdeen.
Receipts
Figures about this page, measured by the server that sent it. None of them are typed in by hand.
- Page weight
- 17.5 KB Everything this page downloads, compressed, in 4 requests. Check it yourself: page weight
- Server render
- 0.58 ms Median of the last 49 renders. 95% took under 1.1 ms. Check it yourself: Server-Timing response header (browser dev tools, Network tab)
- Server memory
- 35.8 MB What this server holds on its own, right now. Node's shared program code is not counted. Check it yourself: server memory
- Third-party requests
- 0 The content security policy lets the browser load from this domain only. Check it yourself: third-party requests
- Runtime dependencies
- 2 69 packages installed in total, counted from the lockfile. Check it yourself: runtime dependencies
- Cookies set
- 0 Out of 1,751 responses since the server started. Check it yourself: Application tab in your browser's dev tools
- Version
- c85c58e Deployed . Check it yourself: version
- DMARC policy
- p=reject Read live from DNS for passionfruit.design. Check it yourself: dmarc policy
- Security headers
- This response was served with:
Content-Security-PolicyStrict-Transport-SecurityX-Content-Type-OptionsX-Frame-OptionsReferrer-PolicyPermissions-PolicyCross-Origin-Opener-PolicyCross-Origin-Resource-PolicyCross-Origin-Embedder-PolicyOrigin-Agent-Cluster
Tell me what you need
A few lines about the project is enough. I read every message myself. I meet people face to face within about 30 miles of Portsmouth, and work with organisations anywhere in the UK. Or email hello@passionfruit.design, or call or WhatsApp 07385 509278 (open WhatsApp).