The first system built on our shared toolkit found four things wrong with it

Abstract composition of scattered translucent rectangles on the left resolving into fewer, larger, aligned rectangles on the right.
What actually reusing our internal packages, instead of just intending to, taught us about them.
A mock Systems Catalogue page showing a grid of product cards: Voyager, Cargo Bay, Beacon, and Habitat. Voyager's card shows a live Deployed badge; the other three show Coming soon.
Voyager is the first system built on a toolkit that was meant to make every one after it faster.

We build each new internal product on a shared set of packages: authentication, permissions, a common frontend layout, the pieces every system needs on day one so nobody rebuilds them from scratch each time. Before Voyager, our first sales CRM, those packages existed and looked done. They were tagged, they had version numbers, they’d been copied into a couple of earlier apps. What they hadn’t had was a new app built against them under real pressure to ship. Voyager was that test, and it didn’t fully pass.

Reuse looks real until the first app actually leans on it

Most consuming apps were floating on the latest unpinned copy of each package rather than a fixed version, which meant a change to any package could quietly break every app using it on its next install. Several packages had exactly one real consumer despite being built as “shared.” One had none at all.

Building Voyager forced the point. Along the way, five of the shared packages went through a genuine compatibility pass for a Laravel version upgrade, something none of them had actually needed to survive before. Three passed clean and shipped. Two were deliberately held back rather than rushed through, because a separate, unrelated app elsewhere in our stack needed its own upgrade decision made first. Reuse that only works in theory doesn’t get caught by looking at it. It gets caught by building something on top of it.

A dependency diagram showing five shared package boxes pointing into a single Voyager app box, each connection labelled unpinned dev-main, with two packages flagged by a warning icon.
Five shared packages, one real consumer to actually test most of them.

The parts that looked shared weren’t

The frontend layout package had the same problem in a different shape. Two apps that were supposed to be consuming it from the shared package had each quietly grown their own near-identical copy of the page shell, navigation, and loading-state code instead. It looked shared because the files had the same names. It wasn’t shared, because a fix to one copy did nothing for the other.

We pulled that shell out into the actual shared package properly, once, rather than patching a third copy into Voyager. It paid off almost immediately: a login page crash that showed up shortly after launch traced back to that same shared layer, and because there was now one real copy instead of three, the fix landed everywhere at once instead of needing to be found and re-applied per app.

A package changelog panel showing three version bumps in sequence, v2.4.0, v2.5.0, and v2.5.1, each tagged Shipped.
Three fixes to the same shared layer in four days, each one landing in every app that used it.

Two bugs that only existed once something real was built on top

A later piece of work needed Voyager to accept API requests from a script instead of only a browser. Building that surfaced a real gap: the shared authentication package had never actually required the library that makes token-based logins possible. It had worked by accident everywhere it had been used so far, because nothing had tried to use it the way Voyager just did. Fixed properly at the package level, not worked around locally, so the next app to need the same thing won’t hit the same wall. The fix itself needed a second correction days later, once we noticed it only worked when installed inside that package’s own repository, not when pulled in as a dependency the normal way, exactly the same class of “looked fine until something real used it” problem as everything above.

A second bug hid behind the first: every permission check silently failed for that new kind of login, blocking even admin users from anything. It traced back to a mismatch in how two of the shared packages expected to be told which login method was in use, invisible until token-based access was a real thing happening rather than a hypothetical.

Final thoughts

Voyager itself, the actual CRM, went from a standing start to genuinely live in about three to four days. That’s the number the whole systems catalogue idea depends on, systems getting faster to build once a real toolkit exists underneath them. But most of those days went somewhere other people might expect. Not into building Companies, Contacts, and a pipeline. Into making four things in the packages underneath actually true, instead of just assumed.

That’s the part we think is worth saying plainly rather than glossing over. “Reuse” isn’t real until the first thing that leans on it either survives or breaks it. Ours mostly survived, and where it didn’t, the next system, the one after Voyager, doesn’t have to find out the same way.

Curious what building on a real shared toolkit could look like for you?

Voyager is live, and everything it’s built on is a little more honest than it was a week ago. If you’re weighing up whether a system like this could be built quickly for your business, we’re happy to talk through what that would actually take.

Ready to build software that fits your business?

Let’s discuss your processes, challenges and goals. We’ll help you identify the right solutions – whether that’s a CRM, ERP, business portal or something entirely unique.

No pressure. Just a friendly conversation.