Architecture · 2026
🍺 Buy me a beer

Full-stack architecture from zero.

A monorepo with four independent sub-projects: static portfolio, headless storefront, e-commerce backend, and CMS. Each with its own stack, pipeline, and deployment. All built from scratch with Claude Code as copilot.

4 sub-projects
5 CI/CD pipelines
6 languages

Four pieces, one ecosystem.

CuevasLab is a monorepo with four sub-projects that work independently but share a domain, visual identity, and pipelines. The separation allows each piece to evolve at its own pace without blocking the others.

CuevasLab full-stack architecture diagram

web/ — Portfolio

Static HTML + CSS + vanilla JS. No framework. Custom design system with dark/light mode, i18n (EN/ES), contact form with reCAPTCHA, and mini-game arcade. Deployed on Vercel.

storefront/ — Store

Next.js with App Router and TypeScript. Headless storefront connected to Medusa.js. 6 languages, 300+ tests (Vitest + Playwright). Server Components by default. Deployed on Vercel with staging at preprod.shop.cuevaslab.es.

shop/ — Backend

Medusa.js (Node.js) as headless e-commerce engine. PostgreSQL 15 + Redis 7. Docker Compose with Nginx reverse proxy and Let's Encrypt SSL. Admin at shop-backoffice.cuevaslab.es. Deployed on Hetzner VPS.

cms/ — Content

Strapi v5 as headless CMS. Manages homepage content (5 blocks, 6 languages) and static page layouts with dynamic zones. Docker Compose on the same VPS as shop/. Admin at cms.cuevaslab.es.

Two providers, zero unnecessary complexity.

The infrastructure is split in two: Vercel for static/SSR applications and a Hetzner VPS for services that need Docker and persistence. No Kubernetes, no orchestrators — Docker Compose is enough for this scale.

Docker Compose stack diagram on Hetzner VPS
Vercel
  • web/ — Static portfolio
  • storefront/ — Next.js SSR/ISR
  • Edge Functions (CV gate)
  • Serverless Functions (contact, arcade stats)
  • Vercel Analytics + KV (Upstash Redis)
Hetzner VPS
  • shop/ — Medusa.js (Docker)
  • cms/ — Strapi v5 (Docker)
  • PostgreSQL 15 + Redis 7
  • Nginx reverse proxy + Let's Encrypt SSL
  • Ubuntu — SSH deploy from CI

Domains

cuevaslab.es

Main portfolio — Vercel

shop.cuevaslab.es

Production storefront — Vercel

preprod.shop.cuevaslab.es

Staging storefront (develop branch) — Vercel

shop-backoffice.cuevaslab.es

Medusa.js admin — Hetzner VPS

cms.cuevaslab.es

Strapi admin — Hetzner VPS

Five pipelines, fully automated.

Each sub-project has its own GitHub Actions pipeline, triggered by path filters. No monolithic pipeline — each change only triggers what it affects. Vercel auto-deploy is disabled for storefront; everything goes through CI.

Pipeline Branch Trigger Steps
deploy-web main web/** npm ci → build → Vercel deploy
deploy-storefront main storefront/** validate → Vercel deploy (prod) → E2E → Lighthouse
deploy-storefront-staging develop storefront/** validate → Vercel deploy → alias preprod → E2E → Lighthouse
deploy-cms main cms/** Docker build+push → SSH deploy VPS
deploy-shop main shop/** Docker build+push → SSH deploy VPS

Merge gates: nothing reaches production without passing checks.

Three strict gates protect the main branch. The flow follows Gitflow: feature branches → develop → release → main. Each step has a quality barrier that must be cleared before moving forward.

1

Green local tests

Before merging to develop: npm test, tsc --noEmit, and next lint must pass. If they fail, fix first. No exceptions.

2

Green CI on develop

Before merging to main: the staging pipeline must have passed on the latest develop commit. This includes deploy, E2E, and Lighthouse. The only exception is hotfixes.

3

Mandatory version tag

Every merge to main carries a semver tag (vX.Y.Z). This enables historical traceability of metrics: unit tests, E2E, and Lighthouse per release.

Technologies chosen with purpose.

Every technology decision has a reason. I didn't pick what's most popular, but what best fits the project goals: learn, demonstrate full-stack capability, and maintain it as a one-person team.

Frontend

  • Next.js — App Router, Server Components, ISR
  • TypeScript — type safety across the entire frontend
  • Vitest — 290+ unit tests
  • Playwright — 40+ E2E tests
  • Tailwind CSS — utility-first styling

Backend

  • Medusa.js — headless e-commerce engine
  • PostgreSQL 15 — primary database
  • Redis 7 — cache and sessions
  • Strapi v5 — headless CMS
  • Node.js — backend runtime

Infrastructure

  • Docker Compose — VPS orchestration
  • Nginx — reverse proxy + SSL
  • Vercel — static + SSR deploy
  • GitHub Actions — 5 CI/CD pipelines
  • Hetzner — Ubuntu VPS

Observability

  • Vercel Analytics — web metrics
  • Google Tag Manager — event tracking
  • Microsoft Clarity — session recordings
  • Lighthouse CI — performance metrics
  • Custom dashboard — historical trends

Conscious trade-offs.

Every architecture is a set of trade-offs. These are the most relevant decisions I made and why.

Monorepo without monorepo tools

I don't use Turborepo or Nx. With 4 independent projects and path triggers in CI, the extra complexity isn't justified. Each sub-project has its own package.json and lifecycle.

Docker Compose instead of Kubernetes

For a one-person project, K8s is over-engineering. Docker Compose + SSH deploy is simple, predictable, and sufficient. If the project scaled to a team, migrating to K8s would be a natural step.

Vercel auto-deploy disabled

All deployment goes through GitHub Actions. This allows including validation (tsc, lint), E2E, and Lighthouse in the pipeline before code reaches production. Full control over what gets deployed and when.

Static portfolio, no JS framework

The portfolio doesn't need React or Next.js. HTML + CSS + vanilla JS is faster, simpler to maintain, and proves that not everything needs a framework. The design system lives in a single CSS file.

Staging on Vercel, not on VPS

The staging storefront uses a Vercel alias (preprod.shop.cuevaslab.es) instead of a second VPS. Zero cost, same infrastructure as production, and the CI pipeline manages it automatically.

Let's talk

Drop me a note — questions, feedback, or just want to say hi.

Message sent! I'll get back to you soon.

Something went wrong. Please try again.