Skip to content
Book a technical review
Aerial night view of a coastal city: a dense grid of orange-lit streets on one side, open black water on the other, joined by a single long illuminated causeway

From agency prototype to production SaaS: rebuilding a recruitment platform that could not scale

A Dubai recruitment startup came to Softwise with a prototype built fast by a previous vendor: paying customers, weekly outages, and no path to new features. We rebuilt it as a production SaaS using a strangler-pattern migration, moving one module at a time while the old system kept serving customers. Every cutover was reversible and customers never saw a migration.

Context

The client and the problem

Anonymized by design: industry and city, not names. The shape of the problem is the point.

The client

A recruitment startup in Dubai matching companies with vetted candidates. The founding team had a vendor build the first version fast. It won real customers, which is exactly when it started failing: timeouts on candidate search, a shared login for all admin roles, and a schema nobody could extend.

The problem

The trap with a successful prototype is that you cannot turn it off to rebuild it. Revenue depends on the thing you need to replace. A big-bang rewrite with a launch day is how companies lose their customer base in one weekend.

The constraints

Paying customers on the old system. Revenue ran through the prototype every day. A launch-day rewrite meant betting the customer base on a weekend cutover, which the client had already been advised to do and rightly refused.

No documentation, one ex-vendor. The prototype came with no docs and an ended vendor relationship. Every behavior had to be recovered from the code and the logs before it could be replaced.

Dubai data residency. The new platform had to host in-region from the first module, not as a later migration, and billing ran on Stripe's UAE stack from day one.

Build

What we built

Decisions, not adjectives. Each one had an alternative we rejected.

Strangler pattern, not big bang

The new platform grew around the old one. A routing layer sent each module's traffic to old or new, and modules moved one at a time: authentication first, then search, then matching, then billing. Every cutover was reversible within minutes. Customers never saw a migration.

Fix the data model first

Most of the prototype's failures traced to its schema. We designed the target model up front and made every migrated module write to it, so the rebuild paid down data debt as it went instead of carrying it across.

Search that holds under load

Candidate search moved from LIKE queries over a flat table to a properly indexed search service. Queries that timed out under modest data volumes return consistently as the candidate base grows.

Roles that mean something

The prototype's shared admin login became proper role-based access: recruiter, hiring manager, and administrator each see and can do exactly what their role allows, with an audit trail underneath.

A cutover playbook per module

Each migration followed the same routine: shadow-read both systems, sync writes, move reads, then flip the route. The playbook made the fourth cutover as boring as the first, and made rollback a routing change rather than an incident.

Strangler-pattern migration: cutover topology and playbookCustomer traffic enters a routing layer that sends each module to either the legacy prototype or the new platform. Modules moved in order: authentication, search, matching, billing. Each cutover followed the same playbook: shadow reads against both systems, write sync, move reads, flip the route, with rollback as a routing change within minutes. The new platform runs on the redesigned data model with indexed search and Stripe billing in-region. Hover or tap a component to trace its connections.NEW PLATFORM — EVERY CUTOVER REVERSIBLE IN MINUTESall trafficmodule 1module 2module 3module 4not yet movedmigrated datachangesdual-writeidentitysessionsindexed docsreads target modelsubscriptionspolicy checkships withCustomersrecruiters + candidatesRouting layerper-module traffic switchLegacy prototypeLIKE-search · shared admin login · frozen schemaAuthenticationmodule 1 — movedIndexed searchmodule 2 — movedMatching enginemodule 3 — movedBilling + Stripe UAEmodule 4 — movedNew data modelredesigned schemaTARGETWrite syncdual-write during cutoverRoles + auditrecruiter · manager · adminNew platform deployin-region, Dubaiplaybook per module: shadow reads → write sync → move reads → flip route → roll back if needed
Strangler-pattern migration of the Dubai recruitment platform: a routing layer moved authentication, search, matching, and billing from the legacy prototype to the new platform one module at a time, with write sync and a per-module cutover playbook keeping every step reversible in minutes and customers never seeing a migration.
Approach

How the engagement ran

Delivery order matters as much as what was built. Each phase had to be safe to stop on.

  1. /001/

    Phase 1 — Audit and target model

    Reconstructed the prototype's actual behavior from code and logs, designed the target schema, and stood up the routing layer with all traffic still on the old system.

  2. /002/

    Phase 2 — Authentication, then search

    Moved authentication first because every later module depends on it, then search because it caused the outages. Both cutovers were reversible and neither was noticed by users, which was the point.

  3. /003/

    Phase 3 — Matching and billing

    Moved the matching engine onto the new data model, then billing last, with Stripe collection running through the new platform before the old billing code was retired.

  4. /004/

    Phase 4 — Decommission and handover

    With all traffic on the new platform, the prototype was frozen, archived for compliance, and its infrastructure retired. Feature work resumed on the new codebase within weeks of engagement start.

Outcomes

Results and key facts

Qualitative outcomes and engagement shape, without invented numbers.

Outages stopped, customers never noticed

The timeout-driven outages ended as search and matching moved onto the new platform, and no cutover was visible to a single customer. Revenue ran uninterrupted through the entire rebuild.

Features shipping again

New feature work restarted on the new codebase within weeks of engagement start, after months during which every estimate on the prototype had kept growing.

A data model that can extend

The target schema absorbed every post-migration feature request without a breaking change, which the prototype's schema could not do even once.

Engagement
Migration, then ongoing feature work
Approach
Strangler-pattern migration, module by module, reversible cutovers
Stack
Next.js, Node.js, PostgreSQL, indexed search, Stripe
Downtime during migration
None planned, none taken
Outcome
Search holds under load, outages stopped, new features shipping again within weeks of engagement start
FAQs

Questions about this saas project.

The questions this project is usually asked about. Still stuck? Ask us directly.

Yes, with a strangler-pattern migration: route traffic module by module from the old system to the new one, keeping every step reversible. The old system keeps serving whatever has not moved yet.

When the data model itself blocks new features, or when reliability failures cost customers. If outages recur in the same subsystems and every feature estimate keeps growing, patching is paying interest on a debt that a rebuild retires.

Yes. The first step is a code and infrastructure audit to decide what survives. Salvaging working modules is cheaper than pride. Rebuilding everything is only right when the audit proves it.

Module by module behind a routing layer. Each module runs shadow reads against both systems, syncs writes, then moves reads, and only then flips the route. Every step is reversible in minutes, so a rollback is a config change, not an incident.

Once every module has moved and the routing layer sends no traffic to it, the old system is frozen, archived for compliance, and its infrastructure retired. Nothing is switched off until nothing depends on it.