Field Notes

A Domain-First Approach to React Native at Kamioun

Aug '25

When I joined Kamioun, the mobile apps were already in a rough state. They’d gone through several engineers, and every hand left its mark. Over time, the project turned into a tangled React Native CLI monolith: huge 400-line files, logic mixed with UI, and no clear architecture at all. You could tell it had been built under pressure — features piled on top of each other with little thought for the long term.

But the frontend was only half the problem. The real pain came from the backend. The apps were deeply tied to a Magento monolith that made every integration unpredictable. Endpoints were inconsistent, the data shape changed between calls, and even a small bug could take hours to trace. Debugging felt like chasing ghosts through a spaghetti forest — you’d start on one feature and end up breaking another three.

When I took over the rebuild and decided to migrate everything to Expo, I knew this was my chance to fix it properly — not just patch the symptoms but design a system that would stay healthy as the company scaled.

The first step was reorganizing the entire project around domains instead of features. Rather than having one giant screens folder and a random utils pile, I split everything into logical business areas — auth, catalog, orders, and dispatch. Each domain became a mini-app in itself, with its own store, API, and components.

This small decision changed everything. Suddenly, I could jump into any domain and understand the logic immediately. If something broke in orders, it stayed in orders — no side effects leaking into auth or catalog. Refactoring became predictable again, and new developers could onboard without needing a map and a week of debugging.

The internal stack also evolved a lot. I used React Query for data fetching and caching, Zustand for lightweight local state, and Zod to enforce type safety and data validation between the backend and frontend. For persistence, I chose MMKV instead of SQLite — it’s faster, simpler, and perfect for the kind of local-first behavior we needed.

That part was key: a lot of our users are corner-shop owners in areas with unstable connectivity. They can’t afford an app that goes blank the second they lose signal. So I designed the retailer app to be local-first — letting users browse, add products, and even queue orders offline, then sync seamlessly once they’re back online.

After a few intense weeks of refactoring, the difference was night and day. The codebase was readable again. The app actually felt fast and reliable. And for the first time, adding a new feature didn’t come with that sense of dread that something else would explode.

This rebuild wasn’t just about making the app work better — it was about restoring confidence in the code. It taught me that great engineering isn’t just about performance or syntax — it’s about structure, predictability, and designing systems that respect the people who will build on them next.