Sometimes the Gross Solution Is the Right One
The right engineering decision is not always the cleanest one. Sometimes the safer move is to contain the mess instead of triggering a risky rewrite.
We recently needed a second app that reused parts of our React admin.
The "correct" solution was obvious.
- extract the shared code into a package
- separate the apps cleanly
- define proper boundaries
We did not do that.
Instead, we put two apps behind a switch inside the same main.tsx. A server flag decides which one to render.
It is gross. We knew it was gross. We shipped it anyway.
And it was the right call.
The problem was not just the code
At the time, extracting the shared parts was not cheap.
The code was not structured for it.
- shared logic depended on app-level singletons
- auth, analytics, HTTP client, and session were all global
- there were no clear injection boundaries
Pulling things out properly would have meant redesigning how half the app worked.
That is not an extraction.
That is a rewrite.
So we did not do it.
We took the shortcut, shipped the second app, and moved on.
Then the situation changed
Later, we actually needed real separation.
A new app. More shared surface. More reason to invest.
This time, the investment made sense.
But here was the catch.
The system we were extracting from was already shaped by the earlier decision.
The singletons were everywhere.
The coupling was real.
And now we had a choice.
The clean solution we did not take
We could have removed the singletons, introduced proper dependency injection, moved things behind providers, and redesigned the infra layer.
That would have looked more correct.
It also would have been:
- slow
- risky
- hard to validate incrementally
- likely to break subtle assumptions
In other words, high blast radius.
What we did instead
We kept the pattern.
But we contained it.
We introduced an admin-infra package that defined clean interfaces for things like auth, analytics, session, and HTTP. The app passes concrete implementations at startup through a setup() function. The package stores them internally, yes, as singletons.
Then the shared code imports hooks and utilities from that package, which read from the configured setup.
So now:
- the boundary is explicit
- the wiring happens in one place
- the shared package is usable
- and internally, it still relies on global state
It is not clean.
But it is controlled.
Why this worked
We did not fix the architecture.
We stabilized it.
- no large refactor
- no need to untangle every dependency
- no breaking of existing assumptions
- migration can happen gradually
Most importantly, the risk is now bounded.
That mattered more than elegance.
The uncomfortable part
Engineers like to fix things properly, especially when something feels wrong.
Singletons feel wrong. Hidden dependencies feel wrong. Global state feels wrong.
So the instinct is simple.
Now that we are touching this, let's fix it.
That instinct is often expensive.
Because the system does not care what feels wrong.
It cares what it already depends on.
What actually changed
Before:
- messy system
- implicit globals
- hard to extract
After:
- still messy internally
- but the mess is centralized
- the dependency is explicit at the boundary
- extraction is now possible
That is real progress.
Even if it does not look clean.
What this is really about
This is not really about singletons.
It is about scope.
We did not need to redesign the system.
We needed to move part of it safely.
Those are very different problems.
The mistake is turning one into the other.
The senior tradeoff
The real decision was not clean versus ugly.
It was controlled mess versus risky rewrite.
Senior engineering is often about choosing based on second-order effects.
- What breaks if this goes wrong?
- How much of the system do I have to understand to be confident?
- Can I roll this back?
Our solution was reversible.
A full refactor probably would not have been.
Boundaries
This approach is not always correct.
If the pattern is actively causing production issues, if the system is small enough to refactor safely, or if you are early enough to fix it cheaply, then fix it.
But when the system already depends on the pattern, the coupling is wide, and the risk is unclear, you do not get points for purity.
Takeaway
If a bad pattern already owns part of your system, your job is not always to eliminate it immediately.
Your job is to contain it, make it explicit, and stop it from spreading further.
Sometimes that means writing code you do not love.
That is fine.
Because the goal is not clean code.
The goal is a system that keeps working while you change it.
Related writing
Constraints Are a Feature, Not a Limitation
Flexible APIs look powerful, but in real systems constraints often create better usability, consistency, and safer defaults.
The Hidden Architecture Inside Product Decisions
Architecture is often just a frozen prediction about the product, which is why small product decisions quietly become long-term technical constraints.
Maybe You Don't Need This Library
Libraries are useful, but they are not free. Sometimes the right move is to write the code yourself first.

