Skip to content
Engineering Atlas
SystemsMagma CRM — Frontend Architecture & Real-Time Systems

Magma CRM — Frontend Architecture & Real-Time Systems

Led frontend architecture for a multi-role CRM, replacing a client-facing REST layer with server-driven data access and building a real-time notification system on Supabase Realtime, while mentoring a two-person frontend team.

Status
Active
Timeline
Jan 2026 – Present
Reading Time
5 min
Domains
Frontend Architecture, Real-Time Systems, Access Control
Technologies
Next.js, Next.js Server Actions, Supabase, Supabase Realtime, TypeScript
Contents

Executive Summary

Magma is a multi-role CRM built at Cloudmate Technologies. Work centered on the frontend architecture: replacing a conventional client-facing REST layer with Next.js Server Actions so no data-fetching endpoint was directly reachable from the browser, building a real-time notification system on Supabase Realtime without flooding the UI with unrelated re-renders, and enforcing role-based access control at the same boundary that fetches data. Also mentored a two-person frontend team through the rebuild.

Business Context

Problem
Magma's frontend talked to its backend through a conventional REST API, which meant every data-fetching endpoint was, by construction, reachable by anyone who could inspect network traffic and hold a valid session — independent of whatever checks the UI performed. As the number of roles and permission boundaries in the CRM grew, that surface became harder to reason about and secure.
Users
Internal staff across multiple roles, each entitled to a different slice of CRM data and actions.
Business Goals
Remove client-exposed data endpoints as an attack surface. Give users live visibility into CRM events without manual refreshing. Keep the frontend maintainable for a small team.
Success Metrics
No CRM data-fetching endpoint directly callable from the browser. Real-time events reflected in the UI without unrelated components re-rendering. Two engineers productive against the new architecture without recurring design questions.
Environment
Production Next.js frontend against a Supabase-backed data layer, used daily by internal CRM staff.
Stakeholders
Frontend team of two engineers, mentored through the transition; CRM end users depending on accurate role-scoped access.

Constraints

No client-exposed data endpoints

Every REST endpoint that returned CRM data was, in principle, callable directly by anyone with the URL and a valid session. The architecture needed to close that off structurally, not by convention or endpoint-level auth checks.

Real-time without re-render storms

Supabase Realtime pushes every relevant database change to subscribed clients. Naive subscription handling meant components re-rendering on changes that had nothing to do with what they displayed.

Small team, shared architecture

The frontend was maintained by two engineers besides me. Decisions had to be simple enough to onboard onto quickly and consistent enough not to require re-deriving the pattern for every new feature.

Role-scoped data everywhere

Every view, list, and action in the CRM had to respect the current user's role. There was no single access-control checkpoint to add correctness at — it had to hold at every data-fetching boundary.

System Blueprint

Server Actions replace direct REST calls between the browser and the data layer, notifications reach the UI through scoped Supabase Realtime subscriptions instead of polling or one broad subscription, and role checks sit at the same boundary as data fetching rather than in the UI.

Frontend

Next.js application rendering role-scoped CRM views, with no direct fetch calls to data-returning API routes.

Next.jsTypeScript

Server-Driven Data Layer

Next.js Server Actions mediate every read and write, enforcing role checks before returning or mutating data.

Next.js Server Actions

Real-Time Layer

Supabase Realtime subscriptions scoped per view, so a component only re-renders on changes relevant to what it displays.

Supabase Realtime

Data Layer

Supabase-backed datastore holding CRM records, roles, and permissions.

Supabase
  • Frontend → Server Actions → Data Layer (no direct client → REST-endpoint path)
  • Data Layer changes → Supabase Realtime → scoped frontend subscriptions → targeted re-render

Architecture

Server Actions as the only data boundary

Every CRM read and write goes through a Next.js Server Action rather than a REST route the client can call directly, so there is no data-fetching URL to expose, guess, or hit with an unauthorized request.

Scoped real-time subscriptions

Instead of one broad Supabase Realtime subscription per session, components subscribe only to the tables and filters relevant to what they render, so a change in one part of the CRM doesn't re-render views that have nothing to do with it.

Role-based access control at the data boundary

Role checks live inside the same Server Actions that fetch and mutate data, so access control can't be bypassed by a component that forgets to check a role before rendering — the boundary that returns data is the boundary that enforces the role.

Team-legible patterns

Server Actions, subscription scoping, and role checks were structured as repeatable patterns rather than one-off implementations, so a two-person frontend team could extend the CRM without re-deriving the architecture for every new feature.

Engineering Decisions

Server Actions vs. a client-facing REST API

Problem
A REST API meant every data-fetching endpoint was reachable directly from the browser by anyone who found the URL, independent of whatever UI-level checks existed.
Options
  • Keep the REST API and rely on endpoint-level auth checks.
  • Move all data access behind Next.js Server Actions, invoked only from the application's own server-rendered flows.
Decision
Replaced the client-facing REST layer with Server Actions.
Trade-offs
Server Actions couple data access more tightly to the Next.js framework, making it harder to expose the same data layer to a future non-Next.js client without rework.
Outcome
There is no longer a data-returning endpoint a client can call outside the application's own server-rendered flow.

Broad vs. scoped Supabase Realtime subscriptions

Problem
A single broad subscription per session meant every database change relevant to the CRM triggered re-renders in components that had nothing to do with that change.
Options
  • Keep one subscription per session covering all CRM tables.
  • Scope subscriptions per component to only the tables and filters that component actually renders.
Decision
Moved to scoped, per-component subscriptions.
Trade-offs
More subscriptions to open and tear down correctly on unmount, instead of one connection to reason about.
Outcome
Components re-render only on changes relevant to what they display, instead of on every CRM-wide event.

Production Stories

This section is planned but not yet documented.

Lessons Learned

  1. Removing a client-exposed API surface is a stronger security property than auth-checking every endpoint on that surface — the endpoint that doesn't exist can't be misused.

  2. Real-time features need subscription scoping designed up front; retrofitting it after every component has already subscribed broadly is a much bigger refactor.

  3. Patterns that are easy to teach are as valuable as patterns that are technically elegant, when the team maintaining them is small.

  • The Endpoint That Doesn't Exist Can't Be Misused

    Auth-checking every REST endpoint is weaker than not exposing one at all — replacing a client-facing REST layer with Next.js Server Actions closed off a class of misuse by removing the surface, not by guarding it.



Command Palette

Search for a command to run...