Four frontends, one address
An organisation's address serves three separate applications:
yourorg.rukialabs.com/ learner
yourorg.rukialabs.com/instructor authoring and grading
yourorg.rukialabs.com/admin administration
They are three builds, from three directories, with three bundles. They share a domain, a session and a design system, and a person moving between them does not sign in again.
Why not one applicationβ
The three audiences barely overlap. A learner never opens the grading queue; an administrator rarely takes a course. Built as one application, everyone downloads all three β and the admin console, the heaviest of them, is the one the fewest people open.
Why not three domainsβ
Because of the session. Three domains means the sign-in state has to be shared across origins, and every mechanism for doing that is either a third-party cookie β increasingly not a mechanism at all β or a redirect dance that is visible to the user and fragile in exactly the situations where it matters.
One origin means one cookie jar. The session problem disappears rather than being solved.
The part that is genuinely awkwardβ
Serving three applications from one origin means each has to know where it lives.
A build served at /admin has to request its assets from /admin/assets/β¦,
and its router has to know that /admin is the root and not a route.
That value is compiled in at build time. Which means the same source, built for a different mount point, is a different artifact β and if the mount point and the build disagree, the failure is not subtle. You get a blank page and a console full of 404s for JavaScript files that exist, at a path nobody asked for.
We have hit this from both directions. A build that captured the browser's path
into a post-sign-in redirect β a path that already contained the prefix β and
replayed it through a router that prepends the prefix itself, landing everyone
on /instructor/instructor/. The learner console escaped it because its prefix
is empty, which is exactly why it took a while to see: the bug was invisible in
the application we look at most.
What holds it togetherβ
A router at the edge decides which application serves a path, and rewrites the asset URLs in the HTML on the way back so the browser only ever sees the public layout. The applications behind it can be one deployment or several, at their own roots or under their prefixes, and the choice is configuration rather than code.
That flexibility earns its keep during migrations, and costs something the rest of the time: there are now two places a mount point is decided, and they have to agree. Our defence is that disagreement fails loudly β an unconfigured application returns an error naming the variable to set, rather than quietly falling back to a default that serves the wrong environment's code under the right hostname.
That specific failure β "not configured" and "working" being indistinguishable β is worth more attention than it usually gets. A fallback that makes a broken configuration look healthy does not prevent the outage. It just moves it to a worse moment.