Skip to main content

Overview

CryptoTracker uses Expo Router for navigation, which implements file-based routing similar to Next.js. Routes are automatically created based on the file structure in the app/ directory.
Expo Router is built on top of React Navigation, providing type-safe routing with automatic deep linking support.

Route Structure

The application’s routing architecture is defined by the following file structure:
app/
├── _layout.tsx           # Root layout (Stack navigator)
├── (tabs)/               # Tab navigation group
│   ├── _layout.tsx       # Tab bar configuration
│   └── index.tsx         # Home route → /
├── crypto/
│   └── [id].tsx          # Dynamic route → /crypto/:id
├── +not-found.tsx        # 404 page
└── +html.tsx             # Custom HTML wrapper (web)

Route Mapping

File PathURLScreen
(tabs)/index.tsx/Home screen with crypto list
crypto/[id].tsx/crypto/90Detail page for Bitcoin
+not-found.tsxAny invalid route404 error page

Root Layout

The root layout (app/_layout.tsx) sets up the base navigation structure and theme configuration:
app/_layout.tsx:65-76
function RootLayoutNav() {
  const colorScheme = useColorScheme();

  return (
    <ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
      <Stack>
        <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
        <Stack.Screen name="modal" options={{ presentation: 'modal' }} />
      </Stack>
    </ThemeProvider>
  );
}
  • Theme Provider: Wraps the app with React Navigation’s theme system
  • Color Scheme Detection: Automatically adapts to light/dark mode
  • Stack Navigator: Provides stack-based navigation with screen transitions
  • Font Loading: Loads custom fonts before rendering the app

Initial Route Configuration

app/_layout.tsx:20-22
export const unstable_settings = {
  initialRouteName: '(tabs)',
};
This ensures the app always starts at the tab navigation group when launched.

Tab Navigation

The tab layout (app/(tabs)/_layout.tsx) creates a bottom tab bar with the FilterProvider context:
app/(tabs)/_layout.tsx:34-55
export default function TabLayout() {
  const colorScheme = useColorScheme();

  return (
    <FilterProvider>
      <Tabs
        screenOptions={{
          tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint,
        }}
      >
        <Tabs.Screen
          name="index"
          options={{
            title: 'Inicio',
            tabBarIcon: ({ color }) => <TabBarIcon name="home" color={color} />,
            header: () => <HomeHeader />,
          }}
        />
      </Tabs>
    </FilterProvider>
  );
}
The (tabs) directory uses parentheses to create a route group. This organizes routes without adding path segments to the URL.

Tab Screen Configuration

<Tabs.Screen
  name="index"              // Route file name
  options={{
    title: 'Inicio',         // Tab label
    tabBarIcon: ({ color }) => <TabBarIcon name="home" color={color} />,
    header: () => <HomeHeader />,  // Custom header component
  }}
/>

Dynamic Routes

The crypto detail page uses dynamic routing with the [id].tsx file naming convention:
app/crypto/[id].tsx:15-26
const CryptoDetailPage = () => {
  const params = useLocalSearchParams();
  const id = Array.isArray(params.id) ? params.id[0] : params.id;

  if (!id) {
    return <Text>No se encontró la criptomoneda.</Text>;
  }

  return <CryptoDetailScreen id={id} />;
};

How Dynamic Routes Work

1

URL Parameter Extraction

The useLocalSearchParams() hook extracts route parameters from the URL:
const params = useLocalSearchParams();
// For /crypto/90 → { id: '90' }
2

Type Safety

Parameters are handled safely to account for array or string values:
const id = Array.isArray(params.id) ? params.id[0] : params.id;
3

Validation

The component validates the parameter before rendering:
if (!id) {
  return <Text>No se encontró la criptomoneda.</Text>;
}
4

Data Fetching

The ID is passed to the detail screen, which fetches the full crypto data:
return <CryptoDetailScreen id={id} />;
CryptoTracker uses the useRouter hook for programmatic navigation:
src/components/CryptoCard.tsx:21-40
const CryptoCard = ({ crypto }: Props) => {
  const router = useRouter();

  return (
    <TouchableOpacity
      onPress={() =>
        router.push({
          pathname: '/crypto/[id]',
          params: { id: crypto.id || '' },
        })
      }
    >
      {/* Card content */}
    </TouchableOpacity>
  );
};
Navigates to a new screen and adds it to the navigation stack:
router.push('/crypto/90');
// or with params object
router.push({
  pathname: '/crypto/[id]',
  params: { id: '90' }
});

Type-Safe Navigation

Expo Router automatically generates TypeScript types for your routes:
import { Href } from 'expo-router';

const cryptoRoute: Href = '/crypto/[id]';
const homeRoute: Href = '/';

Deep Linking

Expo Router automatically handles deep linking. URLs are mapped directly to routes:
Deep LinkRoute FileScreen
myapp://(tabs)/index.tsxHome
myapp://crypto/90crypto/[id].tsxBitcoin detail
Deep linking works automatically on both native platforms and web without additional configuration.

Route Groups

The (tabs) directory demonstrates route groups - a way to organize routes without affecting the URL structure:
app/
├── (tabs)/          # Group without URL segment
│   ├── _layout.tsx  # Layout for this group
│   └── index.tsx    # → /
Without parentheses, the route would be /tabs instead of /.

Benefits of Route Groups

URL Clean-up

Keep URLs clean while organizing code logically

Shared Layouts

Apply common layouts to grouped routes

Context Providers

Wrap specific route groups with providers

Navigation Types

Separate tabs, stacks, and drawers

Custom Headers

Screens can define custom header components:
app/(tabs)/_layout.tsx:49
header: () => <HomeHeader />
This replaces the default navigation header with a custom component that includes search and filter functionality.

Header vs No Header

<Tabs.Screen
  name="index"
  options={{
    header: () => <HomeHeader />
  }}
/>
Here’s a complete navigation flow from home to detail:
1

User Opens App

App starts at root layout (_layout.tsx) which loads fonts and theme
2

Initial Route Rendered

Stack navigator shows (tabs) group, which renders index.tsx (home screen)
3

Home Screen Loads

index.tsx renders HomeScreen component, which fetches crypto data
4

User Taps Crypto Card

CryptoCard calls router.push({ pathname: '/crypto/[id]', params: { id: '90' } })
5

Navigation Transition

Stack navigator pushes crypto/[id].tsx onto the stack with slide animation
6

Detail Screen Renders

[id].tsx extracts ID from params and renders CryptoDetailScreen
7

User Navigates Back

Back button or gesture calls router.back(), returning to home screen

Best Practices

Always use TypeScript types for route parameters:
import { useLocalSearchParams } from 'expo-router';

type Params = {
  id: string;
};

const params = useLocalSearchParams<Params>();
Always validate dynamic route parameters:
if (!id) {
  return <ErrorScreen message="Invalid ID" />;
}
Group related routes together:
app/
├── (auth)/
│   ├── login.tsx
│   └── register.tsx
├── (app)/
│   ├── home.tsx
│   └── profile.tsx
Use _layout.tsx files to share providers, headers, or authentication logic:
export default function AuthLayout() {
  return (
    <AuthProvider>
      <Stack />
    </AuthProvider>
  );
}

Next Steps

Architecture

Understand the overall app structure

State Management

Learn about Context API and hooks

Build docs developers (and LLMs) love