Engineering

One deployment, every gym

How and why we replaced branch-per-customer with multi-tenant subdomain routing. We are an early-stage product — live at one Mumbai gym — and that is exactly why we fixed this now, before the old architecture could calcify.

Where we started

TraqGym's first deployments were one Vercel project, one git branch, and one database per gym. It was the fastest way to get our first customer live, and it worked — but it does not scale past a handful of gyms:

BEFORE: branch-per-customer

  gym-a branch ──> Vercel project A ──> database A
  gym-b branch ──> Vercel project B ──> database B
  gym-c branch ──> Vercel project C ──> database C

  one fix = N merges + N deploys + N migrations

Scroll sideways for the full diagram →

Where we are now

Today there is a single deployment. Middleware reads the Host header, extracts the gym's subdomain, and rewrites the request to that gym's workspace — the marketing site and the tenant workspaces are the same app. Data isolation moves to where it belongs: the database layer, with every row scoped to its gym and every query filtered by tenant.

AFTER: one deployment + subdomain routing

  gym-a.traqgym.com ────────┐
  gym-b.traqgym.com ────────┼──> middleware ──> one Next.js app
  {your-gym}.traqgym.com ───┘    Host header        │
                                 rewritten to       ▼
                                 /gym/{slug}    one database
                                                (rows isolated per gym)

  one fix = one merge + one deploy + one migration

Scroll sideways for the full diagram →

Honest status: the *.traqgym.com wildcard still points at the legacy per-gym setup, so tenant subdomains do not resolve against this deployment yet. Until the cutover below completes, workspaces are served from this deployment at /gym/{slug} (see the demo at /gym/demo), and we activate each gym's subdomain during onboarding.

Migration path for our live gym

Our live gym currently runs on the old branch-per-customer setup, and it stays there until cutover is proven. The plan, in order: export the gym's database and import it into the shared database under the gym's tenant scope; run the subdomain workspace read-only against the migrated copy and reconcile it against the live system; then point the gym's subdomain at the shared deployment and retire its branch, with the old deployment kept warm as a rollback until the cutover has survived a full billing cycle.