Website Design / Website Development / E-Commerce / SEO / Web Support / 2026
Inspiration Africa: nine Next.js apps, rebuilt as one Laravel platform
Client: Inspiration Africa
Inspiration Africa sells safaris through nine brands. Each one had its own Next.js application — 535 pages and 1,212 API route handlers across the nine, nine deployments, and every shared change made nine times. We rebuilt the whole group as a single Laravel application: 75 models, 332 screens, 854 tests, one database and one server, with the brand resolved from the request's Host header. Nine front doors, nine designs, one thing to maintain.
The challenge
The nine brands were not variations of a website. They were nine websites.
The source was a Turborepo holding 9 Next.js applications — 535 page components, 1,212 API route handlers, 596 components inside the apps and 38 shared between them — all pointed at one Postgres with 64 Prisma models and 33 enums, and deployed as nine separate projects. A tour lived in one database, which was right; the code that displayed it existed nine times, which was not.
The cost of that shows up in ordinary work. Fixing a booking form means finding it nine times, or finding the eight places it was copied and the one place it drifted. Adding a payment state means nine deployments. A security fix is nine pull requests. And because nobody can hold nine codebases in their head, the brands quietly diverge — not by design, but by whichever one someone last had open.
The approach
The rule that made everything else possible: the brand is a property of the request, not of the deployment. A middleware reads the Host header, looks it up in a table of domains, and binds an immutable site context for the rest of that request. Routes are host-agnostic — one route set serves all nine — and the controller picks which brand's React pages to render. Adding a tenth brand is a row and a folder, not a repository.
That let the catalogue stay one catalogue while the shelves differ. Three patterns cover the nine: brands that share /safaris, brands with their own path (Kenya at /kenya-safaris, Kilimanjaro at /climbs), and /tours dispatch — where the hub sees everything and a spoke sees only its own. The wrong path on the wrong brand returns 404 rather than quietly working, which is the difference between a system and a coincidence.
Then the parts that carry money. One state machine is the only thing allowed to move a booking between statuses — eight of them, with an explicit table of which moves are legal and fourteen of those moves each mapped to the message it sends. It stamps the timestamps, writes the audit line and dispatches the emails, and totals are always recomputed server-side from the tour's own pricing tiers rather than trusted from the browser. Mail goes through a durable outbox — the row is written before the send is attempted — so a bounced SMTP connection loses a delivery attempt, never a record.
And the move itself: 64 Prisma models re-expressed as 75 Eloquent models over 90 migrations, the production data imported and reconciled to the cent, per-brand analytics and sitemaps ported, a cross-brand parity suite that asserts each brand's routes and catalogue isolation, and a cutover runbook with an executable pre-flight gate. 854 tests across 136 files, 190 commits.
The results
Nine brands now run from one codebase on one server against one database. 332 Inertia screens replaced 535 Next.js pages: 230 across the nine public brands, 82 in the admin, 8 in the operator portal and 5 in the partner portal. 112 controllers replaced 1,212 API route handlers. 20 public hostnames — nine canonical, nine www-or-apex aliases, and a misspelled domain the group owns along with its www — all resolve through the same application, publishing 990 URLs between them.
On the hub's homepage the script count fell from 39 tags to 8, and the readable text the server sends rose from 6,465 characters to 9,581 — more of the page arrives finished, with less JavaScript needed to finish it. Each brand keeps its own analytics property, its own sitemap and robots, its own palette and type, and its own unique pages; none of them can see another's rows, in public or in the admin.
What did not get smaller, and it is the first thing an engineer will check: the hub homepage's HTML went from 126KB to 255KB. Some of that is the page data Inertia ships alongside the markup, and the rest is that the new homepage simply has more of the page in it. It is a real cost and the next round of work is to make it smaller.