How we built the Vinode SaaS: four years and three rewrites

Key Takeaways
- A measured peak of 5,000 concurrent users per project showed that CDN delivery and small APIs mattered more than independently deployed services.
- Moving a static schema from MongoDB to PostgreSQL cut 90,000 lines and made synthetic round trips 12.5 times faster.
- Once our tooling stopped being the bottleneck, work that used to take weeks landed in days.
Four years of engineering produced the system behind a Vinode project: CRM, CMS, dashboards, sales and marketing tools, plus a no-code editor where sales and marketing teams can build interactive pages, lead capture and live unit content themselves. That self-service platform is releasing soon.
The product definition was already mostly settled in year one. We spent much of the time removing machinery that made agreed work slow to ship. Three rewrites changed the consequence for a client: more of the product could be built, and later maintained, without waiting for us to fight our own stack.
We began on 5 July 2022. The first work was Auth because access and identity had to be safe before anything else. Organization and Project followed, then the standalone Player and Storage modules that deliver an experience from a business to its eventual buyer.
We designed for imagined scale
The backend started as true microservices in .NET 6. Every module was a separate executable, and an internal event bus carried messages between them. In plain terms, even closely related parts of the product had to communicate across process boundaries.
For about a year, backend and frontend SaaS teams built the core while a separate services team delivered real client projects from a common template. Their backbone was Strapi, with productivity tools built quickly because delivery deadlines do not wait for platform architecture. One group optimised for the future platform; the other had to ship now. Both views shaped the product, but they also pulled us toward flexibility we had not proved we needed.
The test we should have applied earlier
- Measure the load shape before choosing a distributed architecture.
- Check whether schema flexibility is a real requirement.
- Count how much code exists only to translate between the tool and the domain.
The first correction came from measuring the real load. Our busiest project peaks at 5,000 concurrent users, mostly requesting content that a CDN serves well, with small API calls for unit reservation and status check. Buyer delivery needed to scale; separate scaling for every SaaS module added no value.
So we rewrote the backend as a modular monolith. Each module stayed in its own .csproj, meaning it retained a separate C# project boundary, but the network-like event bus between modules disappeared. We kept Wolverine for background jobs, work that can continue outside the user's immediate request, instead of using it to make neighbouring modules talk.
The trade was deliberate. We gave up independent deployment of every module and removed distributed-system failure modes such as a message arriving twice or late. We learned that a process boundary should be earned by a real need for independent scaling, deployment or failure isolation. Our workload had not earned it.
The database was charging us for flexibility
The larger correction came in 2024. We had chosen MongoDB, partly because the services team's world pulled that way. After two years, our schema was static. Document flexibility brought no benefit, yet a method could spend hundreds of lines joining, aggregating, splitting and converting documents before it reached its actual business rule. That was the daily pain: ordinary relationships had become application code.
We spent two months moving every endpoint and every logic path to PostgreSQL. The replacement uses Entity Framework Core with LINQ over Npgsql. LINQ is C#'s typed query syntax; Npgsql connects that query layer to PostgreSQL. The data model was the consequential change. Relationships moved back into a relational database, where the compiler and query planner could help us instead of every method assembling them by hand.
Request time fell from 2.5 seconds to 200 milliseconds in synthetic tests of our workload. Production-user telemetry was outside the test.
The migration cut 90,000 lines from our codebase. MongoDB query code had been 60% of it; the Entity Framework Core equivalent is 20% of the resulting logic. Work that had taken weeks moved in days. Research that had taken days moved in hours. Endpoint after endpoint. Method after method.
Those gains came from our static, relationship-heavy workload. Generic database benchmarks measure a selected set of queries; they cannot include the engineering cost of expressing our particular relationships in application code. Our 12.5× result belongs to this codebase and workload. It is not a universal ranking of MongoDB and PostgreSQL.

We stopped fighting the frontend
The frontend moved from Next.js to pure React, then to TanStack Start. This was the best decision we made for this stage of the product. TanStack Router gives us type-safe file-based routes. A route parameter or search value with the wrong shape fails during development, before it can become a production surprise. Route loaders also put each data dependency beside its route, where we can see and test it. The TanStack comparison documents those mechanics.
This was a fit judgment for our team. Next.js remains sensible when its conventions match the application, especially for teams that want those defaults. Our feature work benefited from explicit loaders and end-to-end route types. We spent less time negotiating framework behaviour and more time implementing the product already in the specification.
What a client is buying now
Once the tooling stopped consuming the schedule, we built out the CRM, CMS, user panels, dashboards, sales tools, marketing tools and the core product. Video Graph became Video Nodes, then Vinode the best platform for off-site sales and marketing. The backend now targets .NET 10. Over four years we delivered projects for more than 20 clients; 13 are currently featured on the Vinode projects page.
Architecture can never guarantee permanent stability. The current platform reflects the load we measured, keeps relational data in a relational store, and makes frontend routes and data dependencies explicit. The upcoming self-service release puts that work in the hands of partners and, on request, client teams: they can build and edit interactive pages, lead forms and unit listings in a no-code editor instead of treating every change as an engineering request.
This route is wrong for a team with independently scaling domains, a genuinely variable document schema, requirements that still change every week, or no measured architectural drag. Microservices and MongoDB solve real problems. Rewriting working software solely for cleaner code would be waste.
There may be another rewrite one day; promising otherwise would turn architecture into marketing. Before considering one in your own stack, inspect a recent month of delayed work and separate requirement changes from tooling drag such as deployment coordination, repeated query code, request time or research time. Do not rewrite on instinct. Find the measured constraint that repeatedly turns planned work into weeks, then use that same measure to judge whether the migration paid for itself.
See whether Vinode fits your project
Tell us the project size, available 3D assets, and the sales experience you need. We will scope a custom quote.

Gaussian Splatting for Real Estate: A Reality-Capture Layer, Not a Replacement for Pre-Rendered 3D
Capture-vendor pages frame Gaussian splatting as the technology set to retire architectural renders. It cannot, and the reason is not image quality. A splat only reconstructs something that already physically stands, which rules out every unbuilt building a developer sells off-plan.

Core Web Vitals for property sites: how to actually measure a 3D tour's impact
One green desktop Lighthouse run on the tour page is not proof the tour is fine. Lab tools and field data answer different questions, and the metric a 3D tour is most likely to fail — INP — is one Lighthouse cannot score at all. Here is how to read the field data at the 75th percentile, and why a freshly launched development page shows "No Field Data" for weeks.

The asset budget is what breaks 3D at 500 units, not your polygon count
Grinding on the model has a hard ceiling at masterplan scale. LOD tiers, a texture memory budget, draw-call limits, and streaming order decide whether a 528-unit scene loads on a buyer's phone. Here is what that budget looks like at 25 buildings, and why no amount of tuning upgrades the device in their hand.
