The largest system I have built, and the one I am working on now. Exelerating is an investment intelligence platform: institutional investors, the people inside them, the mandates they run and the connections between them — searchable, monitored over time, and replacing a legacy estate that answered none of those questions well.
The technical problem
- The questions are about relationships, not rows. “Which pension funds changed fiduciary manager last year, and who sits on both sides?” is a graph traversal wearing a report's clothing. A schema optimised for listing entities cannot answer it; the data model had to make entities, people, mandates and their edges first-class from the start.
- Two products share one codebase. A client-facing platform and an internal CMS have different users, different permissions and different rules, but the same domain underneath. Left unmanaged, that is exactly how an application turns into a tangle where an admin change breaks a client page.
- A live legacy system to migrate from. Years of data in MSSQL had to move to Postgres without inventing history or losing it, while the old system was still in use — so the migration had to be repeatable and reconcilable, not a one-shot import.
- Data-heavy pages must still feel fast. Tables of thousands of rows, a force-directed network graph and cross-entity search are each capable of making a page unusable on their own.
How I solved it
- Enforced architecture: layered clean architecture over the Next.js App Router — app → presentation → application → infrastructure → core, with dependencies pointing inward and the boundaries enforced in the lint configuration. The rule fails a build rather than relying on anyone remembering it, which is what keeps two products in one codebase from bleeding into each other.
- Security at the database, not the route: Supabase Postgres with row-level security throughout, so access rules live where the data does and an admin surface and a client surface cannot accidentally share a door.
- A schema built to evolve: a five-file baseline plus incremental migrations, and typed access from the application layer so a schema change surfaces as a compile error rather than a runtime surprise.
- Validation at the boundaries: Zod schemas where data enters, TanStack Query for server-state and caching, TanStack Table for the data grids — so components render state they can trust.
- Migration tooling: scripts that move and reconcile MSSQL data into Supabase and can be re-run, which is what makes cutting over from a live legacy system survivable.
- A deliberate rendering strategy: a documented per-route decision on static, dynamic and client rendering with a data-loading budget, because on a data-heavy product that is an architectural choice, not a performance afterthought.
- Network graph: a force-directed view over entities and people, for the relationship questions a table genuinely cannot express.
- Mobile: a Capacitor Android shell around the web app, so one codebase ships to a device.
Why it matters
This is the project where the architecture has to hold under real weight: a thousand-plus commits, a schema that keeps moving, two products with different rules, and a legacy system being retired underneath it. Most of the engineering judgement I would bring to a team is visible here.
Stack
Next.js 16 · TypeScript · Supabase (Postgres, Auth, RLS) · TanStack Query · TanStack Table · Zod · Tailwind CSS · d3-force · Capacitor · Vitest · Vercel