ScaleRocket/Mobile

Navigation

File-based routing with Expo Router — tab navigation, stack navigation, and auth-protected routes.

Navigation

ScaleRocket Mobile uses Expo Router for file-based routing, similar to Next.js. Files in the app/ directory automatically become routes.

Expo Router Conventions

  • _layout.tsx — Defines the navigator for a directory (Stack, Tabs, Drawer)
  • (group)/ — Route groups (parentheses). Groups routes without adding a URL segment
  • index.tsx — Default route for a directory
  • Files = routeslogin.tsx becomes the /login route within its group
Root Stack (_layout.tsx)
├── (auth) Stack
│   ├── login
│   ├── register
│   └── forgot-password
└── (tabs) Tab Navigator
    ├── index (Home)
    ├── profile (Profile)
    └── settings (Settings)

The root layout renders a Stack with headerShown: false. Each group defines its own navigator type.

Route Groups

GroupNavigatorPurpose
(auth)StackLogin, register, forgot-password. No header.
(tabs)TabsHome, Profile, Settings. Tab bar at bottom.

Groups with parentheses do not add path segments. Both (auth) and (tabs) are at the root level.

Tab Navigation

The tab navigator is defined in app/(tabs)/_layout.tsx:

import { Tabs } from "expo-router";
import { Ionicons } from "@expo/vector-icons";

export default function TabLayout() {
  return (
    <Tabs screenOptions={{ tabBarActiveTintColor: "#2563EB" }}>
      <Tabs.Screen
        name="index"
        options={{
          title: "Home",
          tabBarIcon: ({ color }) => (
            <Ionicons name="home" size={24} color={color} />
          ),
        }}
      />
      <Tabs.Screen
        name="profile"
        options={{
          title: "Profile",
          tabBarIcon: ({ color }) => (
            <Ionicons name="person" size={24} color={color} />
          ),
        }}
      />
      <Tabs.Screen
        name="settings"
        options={{
          title: "Settings",
          tabBarIcon: ({ color }) => (
            <Ionicons name="settings" size={24} color={color} />
          ),
        }}
      />
    </Tabs>
  );
}

Stack Navigation

The auth group uses a stack navigator in app/(auth)/_layout.tsx:

import { Stack } from "expo-router";

export default function AuthLayout() {
  return (
    <Stack screenOptions={{ headerShown: false }} />
  );
}

Navigate between screens using the router or Link:

import { router } from "expo-router";

// Programmatic navigation
router.push("/(auth)/register");

// Or use the Link component
import { Link } from "expo-router";
<Link href="/(auth)/register">Create account</Link>

Auth-Protected Routes

The auth guard in app/_layout.tsx uses useSession() to check the user's authentication state:

import { useSession } from "../lib/auth";
import { Redirect } from "expo-router";

export default function RootLayout() {
  const { session, loading } = useSession();

  if (loading) return <ActivityIndicator />;
  if (!session) return <Redirect href="/(auth)/login" />;

  return <Stack screenOptions={{ headerShown: false }} />;
}

The tabs layout also has a secondary guard: if the session is lost while on a tab screen, it redirects to login.

Adding New Screens

To add a new screen:

  1. Create a file in the appropriate group (e.g., app/(tabs)/analytics.tsx)
  2. Export a default React component
  3. The route is available immediately — no router configuration needed
  4. Add it to the tab navigator in _layout.tsx if it should appear as a tab
// app/(tabs)/analytics.tsx
export default function AnalyticsScreen() {
  return (
    <View>
      <Text>Analytics</Text>
    </View>
  );
}

On this page