DEV Community

Ron (teRON) Bullock
Ron (teRON) Bullock

Posted on • Edited on • Originally published at teronbullock.com

Why I Deleted My Custom "Crew" Logic Mid-Build

Overcoming sunk cost fallacy before shipping

I was deep into building a custom "Crew" management system for my Next.js SaaS, a job and invoice tracker for small businesses. Everything was all set. My hooks were in place state was wired up and my UI components were all working.

I looked closer at my auth layer and realized my provider better-auth's native organization plugin already handled multi-tenant access control. This was the exact problem I was solving from scratch.

The Mid-Build Pivot

At that point I had two choices:

  • Keep the custom implementation and own it long-term
  • Adopt better-auth's native organization plugin and retire my abstraction

The trade-off was obvious. My custom "Crew" layer duplicated what better-auth was already handling; the member schema, tenant context, permission propagation.

Keeping it meant owning edge cases, permission logic, and future schema drift. The Pivot meant letting the library handle what it was built for.

Why the Native Approach Won

I wasn’t evaluating a dozen alternatives. I was already on better-auth for auth and session management. The organization plugin just extended that to tenant context.

The pivot wins were:

  • Eliminating a redundant abstraction instead of maintaining parallel systems
  • Staying aligned with better-auth's data model instead of fighting it
  • Reducing long-term maintenance across auth, sessions, and permissions in one move

What Improved

Simplified State Management
Removing the custom Crew layer cut unnecessary renders and tightened component isolation across authorized views. Less state to thread through, cleaner boundaries.

Stronger Authorization Boundaries
I already had Zod validation at the API boundary in my custom solution; carrying it forward here just meant it now worked in concert with better-auth's permission model rather than around it.

That gave me cleaner, more reliable schema enforcement for:

  • Member invitations
  • Role assignments
  • Member asset edits (passwords, profile info)

Tradeoffs

The main tradeoff was reduced flexibility for highly custom role logic. But in exchange, I gained consistency, less code to maintain, and tighter integration with the rest of the auth system.

My Takeaway

It’s easy to get attached to a solution after you’ve put hours into it. But knowing when to pivot is key. Sometimes the better solution is the code you never have to write.

Top comments (6)

Collapse
 
itskondrat profile image
Mykola Kondratiuk

this is the right call and also really hard to make mid-build when you already have something working. the sunk cost is real. i think the signal is: if you found the native plugin before shipping to users, deleting is always cheaper than it feels. after you have users depending on the custom behavior, the calculus flips completely.

Collapse
 
amelia2802 profile image
Amelia Dutta

🔥🔥🔥

Collapse
 
klement_gunndu profile image
klement Gunndu

Killed a custom RBAC layer last year for the same reason — realized the auth library already handled 90% of the edge cases I was reimplementing. The hardest part was admitting the sunk cost, not the actual migration.

Collapse
 
peacebinflow profile image
PEACEBINFLOW

This is such a classic "developer's trap," and I love that you caught it mid-flight. There’s a certain high we get from architecting a custom solution—it feels like we're building the "soul" of the app—but usually, we're just reinventing a wheel that a specialized library has already stress-tested.

The "Sunk Cost" of Custom Logic
It’s incredibly hard to delete code that actually works. When your hooks are wired and the UI is snappy, the brain wants to justify the effort. But you hit on the real truth: code is a liability, not an asset. Every line of custom "Crew" logic you wrote was another line you’d have to debug at 2 AM six months from now when a weird permission edge case popped up.

Aligning the Data Streams
By pivoting to better-auth, you shifted from managing state to defining policy. It moves the problem from the "how" (syncing schemas, propagation) to the "what" (who can see this invoice?). This is a huge win for long-term velocity. When the auth provider and the organization logic share the same "DNA," you stop fighting the friction between two different mental models of a "User."

The "Invisible" Performance Gain
I’m glad you mentioned the simplified state management. We often underestimate the cognitive load—and the literal re-render cycles—of maintaining a parallel tenant context. Stripping that out doesn't just make the code cleaner; it makes the application's "thinking" more direct.

A Quick Reflection
I wonder if this pivot will change how you approach the next big feature? Sometimes the best architectural "work" we do isn't the code we commit, but the documentation we read that convinces us not to build.

Kudos for having the discipline to choose maintenance over ego. That’s how real SaaS products actually cross the finish line.

Collapse
 
j3ffjessie profile image
J3ffJessie

The pivot is always a struggle because you worked hard on something but usually the best decision you can make for your project. Aweosme write up.

Collapse
 
thefastfirewatchcompany profile image
The Fast Fire Watch Company

Good App!