mobile

What I set up first when bootstrapping a React Native app

The order I follow when bootstrapping a production React Native app, and why the sequence matters more than the exact library choices.

The hardest part of bootstrapping a React Native app is not choosing tools. There are too many good options and too much community opinion for that to be the genuinely interesting question. What I have found more important — and harder to learn without building several apps — is the question of order: in what sequence do you make foundational decisions so that the app does not fight you later?

After bootstrapping both the consumer PickYourTrail app and internal tooling across Android and iOS, I have become convinced that setup order matters more than the specific library choices within each category. The wrong sequence makes later changes expensive even when the original tool selection was reasonable. This is my current thinking about what belongs early and why.

Navigation is the first thing I establish because it is one of the first real shapes of the app. Before many screens exist, the routing model, screen registry, and nested flow structure all need to be decided. React Navigation has been a good fit, but the specific library matters less than making the decision before you have a lot of screens that would have to be restructured to accommodate it.

Building screens first and navigation second consistently produces rewrites. You make assumptions about how flows work while building the first few screens, those assumptions turn out to be wrong for the actual navigation model, and then you pay the cost of undoing them. Getting navigation in place first gives the app a skeleton that everything else hangs off of. Subsequent screens just add to it rather than each one requiring a round of integration thinking.

Theme and design tokens before components

Before I build more than a couple of screens, I want theming and design tokens defined. Spacing scale, typography sizes, colors, and the assumptions that components will share — if those emerge screen by screen rather than being established first, you get drift. Two screens will use slightly different padding, three different color values for what should be the same thing, and font size assumptions baked into components that are hard to change later without touching every component.

Setting up a token layer early feels slower at the start. It is paying a cost up front that you could defer by just writing magic numbers into the first few screens. But it pays back disproportionately when the design changes, which it always does, and you can update a token value in one place rather than hunting across a codebase.

This is one of the clearest examples of setup order mattering more than setup quality. A good token system implemented after fifty screens is much less useful than a decent token system implemented before any of them.

State management before data fetching

I want to decide where state lives before I start wiring the whole app to APIs. The reason is straightforward: network state and application state have different shapes and different management needs, and if application state does not have a model before network state starts spreading, every feature invents its own storage habits. You end up with global variables, Context everywhere, local component state that should have been lifted, and queries scattered through the component tree.

Jotai has been a good fit for the apps I have worked on because it keeps state modular — individual atoms rather than one large store — and stays readable without a lot of ceremony. That does not mean every app should use Jotai. The specific choice matters less than establishing the model clearly before feature development starts in earnest.

The specific thing I want decided: where does server state live versus UI state versus user preferences? Those are different problems and they deserve different answers. Getting those boundaries clear before the app has much data flow makes every subsequent feature decision cleaner.

Auth flow before anything user-facing

Login is not the most exciting thing to build, but I set it up early because auth affects almost everything else in the app. Startup flow, persisted token state, protected navigation, API setup, error handling for expired sessions, first-run experience — all of those are shaped by decisions made in the auth layer. If the auth flow is vague or deferred, those concerns get patched in later rather than designed once, and they interact with each other in ways that are hard to debug after the fact.

The things I want clearly established in the auth setup: how tokens are stored and refreshed, how the navigation stack responds to logged-out state, what happens on startup before authentication state is known (the splash/loading window), and how the rest of the app accesses auth context. All of those create dependencies that are much easier to build from the start than to retrofit.

Build tooling earlier than most teams do it

This is consistently where I see teams wait too long. Fastlane setup, CI pipeline, signing configuration, and release process often get treated as late-stage work because they feel like deployment concerns rather than development concerns. But by the time you get to them, the app has months of assumptions baked in, and things that should have been simple — like setting up an internal distribution track — turn out to require changes across the build configuration.

Mobile delivery is not an afterthought. iOS and Android release behavior, over-the-air update cadence, build variant management, and signing for multiple environments are part of the product lifecycle from the beginning. Setting up Fastlane lanes and CI for test builds in the first week, even when they are very simple, means those systems mature alongside the app rather than being shoehorned in before a launch under time pressure.

Respect the native layer from day one

There is no excitement in the Gradle and Podfile work, but it pays off consistently. The native setup in React Native projects that were configured casually at the start becomes a recurring pain point: every React Native upgrade requires touching the native layer, every new dependency that has a native module reveals whatever was done inconsistently early on, and environment parity between development and production CI requires a clean native configuration.

Spending the time early on clean Gradle configuration, a sane Podfile, proper signing setup for development versus production, and disciplined platform-specific config is foundation work. It is invisible when it is done well and very visible when it is not.

What I regret setting up late

The things I consistently regret setting up too late are the ones that sit between app behavior and production reality: push notification shape, analytics and error reporting, release automation, and deep linking assumptions.

These feel optional early in development because the core features of the app work without them. But they affect how real users experience the app and how the team operates it, and adding them later requires touching behavior that has already settled. Deep linking assumptions in particular are expensive to add retroactively because they require thinking about every screen in the navigation graph simultaneously rather than just the two or three you built first.

The principle I have arrived at after building several apps across different scales: get the app skeleton, state boundaries, auth flow, native foundation, and release path in place before too much feature code lands on top. The specific library choices within each of those categories matter less than I used to think. The order matters because it determines whether each decision you make is building on a stable foundation or creating a dependency that will resist changing later.