If you've built more than one React Native app, you know the feeling.
You start a new project, get to the navigation setup, and suddenly 2 days are gone. Not because you're slow. Because React Navigation has moving parts that nobody explains together in one place.
Nested navigators. Auth switching. Typed routes. Where does this screen go? Why is the tab bar showing on this screen? Why does going back take me to the wrong place?
I've been there. Every project. Same pain, different client.
So when I started building my open source React Native starter kit, I decided to figure this out properly once and for all and document every decision along the way.
Here's what I learned.
The Mental Model:
Before touching any code, you need one simple mental model.
Think of navigation like a stack of cards on a table.
A Stack Navigator is cards stacked on top of each other. Going back removes the top card.
A Tab Navigator is multiple piles side by side. Switching tabs doesn't stack — it just switches which pile you're looking at.
A Nested Navigator is when a card in one stack is itself an entire new stack. This is where most people get confused. Once that clicks, everything else makes sense.
Three Questions Before Writing Any Code
Before I build any navigation now, I ask three questions:
- Can the user go back from this screen? If yes, it belongs in a Stack Navigator.
- Is this screen inside or outside the tabs? Screens inside the tabs go in the Tab Navigator. Screens that should cover the tabs entirely — like a details page — go in the Stack above the tabs.
- Does authentication affect what the user sees? If yes, you need a Root Navigator that switches between an Auth stack and an App stack.
The Folder Structure That Changed Everything
This was my biggest relief. I used to dump everything into one navigation file. It worked until it didn't.
Here's the structure I use now:
src/
└── navigation/
├── index.tsx ← RootNavigator
├── AuthNavigator.tsx ← Login, Register
├── AppNavigator.tsx ← Authenticated screens
├── BottomTabNavigator.tsx ← Tab screens
└── types.ts ← All route params typed
Each file has exactly one job. When something breaks you know exactly which file to open. When you want to add a screen you know exactly where it goes.
Types First — Always
This is the step I used to skip. Do not skip this one.
tsexport type AuthStackParamList = {
Login: undefined;
Register: undefined;
};
export type AppStackParamList = {
BottomTabs: undefined;
Details: { id: string; title: string };
};
export type BottomTabParamList = {
Home: undefined;
Explore: undefined;
Profile: undefined;
};
undefined means the screen takes no params. Everything else defines exactly what params are required.
Without this you're navigating blind — typos in route names cause runtime crashes, missing params only surface when a user hits that screen. With this, TypeScript catches every mistake before the app runs.
Building the Navigators
*1. AuthNavigator *
tsxconst Stack = createNativeStackNavigator<AuthStackParamList>();
const AuthNavigator = () => {
return (
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="Login" component={LoginScreen} />
<Stack.Screen name="Register" component={RegisterScreen} />
</Stack.Navigator>
);
};
**2. BottomTabNavigator**
tsxconst Tab = createBottomTabNavigator();
const BottomTabNavigator = () => {
return (
);
};
**3. AppNavigator** (can access only after authenticated)
tsxconst Stack = createNativeStackNavigator();
const AppNavigator = () => {
return (
);
};
This is the key insight — BottomTabNavigator is just a screen inside AppStack. Any screen you add above it (like DetailsScreen) will cover the tab bar entirely.
The Auth Switch — This was the most confusing part for me
**4. RootNavigator **
tsxconst RootNavigator = () => {
const { isAuthenticated } = useAuthStore();
return (
{isAuthenticated ? : }
);
};
That's it. No navigation.navigate('Home') after login. No manually resetting the navigation stack. When isAuthenticated flips to true React re-renders and the entire navigator switches automatically. Same for logout — flip it to false anywhere in the app and the user is back at the login screen instantly.
This pattern alone would have saved me days of debugging across multiple projects.
Common Mistakes
- Putting everything in one file. It works until the project grows. Split it from day one.
- Navigating manually after login instead of using the auth switch pattern. Let state drive navigation, not imperative calls.
- Not typing your routes. The 10 minutes it takes upfront saves hours of runtime debugging.
- Putting detail screens inside the tab navigator. They belong in the AppStack above the tabs so the tab bar disappears correctly.
Wrapping Up
Navigation in React Native isn't hard. It just needs the right mental model and a clear structure before you write the first line.
I'm building this as part of my open source React Native starter kit — documenting every decision as I go. If this was useful, follow along.
GitHub: github.com/irf0/react-native-starter-kit
X: @mirfandev5
LinkedIn: Mohammad Irfan
This article was originally published by DEV Community and written by Mohammad Irfan.
Read original article on DEV Community