Skip to main content

Navigation

Expo Router provides multiple ways to navigate between screens: declarative Link components and imperative router methods. The Link component provides declarative navigation:
import { Link } from 'expo-router';
import { Text } from 'react-native';

export default function Home() {
  return (
    <Link href="/about">
      <Text>Go to About</Text>
    </Link>
  );
}

Basic Usage

import { Link } from 'expo-router';

// Simple text link
<Link href="/about">About</Link>

// Link with custom styling
<Link href="/profile" style={{ color: 'blue', fontSize: 16 }}>
  View Profile
</Link>

Using with Custom Components

Use asChild to render a custom component:
import { Link } from 'expo-router';
import { Pressable, Text } from 'react-native';

export default function Home() {
  return (
    <Link href="/about" asChild>
      <Pressable style={styles.button}>
        <Text style={styles.buttonText}>About</Text>
      </Pressable>
    </Link>
  );
}
Pass parameters using the href object:
// String with query params
<Link href="/profile?id=123&name=John">
  View Profile
</Link>

// Object with pathname and params
<Link
  href={{
    pathname: '/profile/[id]',
    params: { id: '123', name: 'John' }
  }}
>
  View Profile
</Link>

Dynamic Hrefs

Construct hrefs dynamically:
import { Link } from 'expo-router';
import { View } from 'react-native';

const users = [
  { id: '1', name: 'Alice' },
  { id: '2', name: 'Bob' },
];

export default function UserList() {
  return (
    <View>
      {users.map((user) => (
        <Link
          key={user.id}
          href={{
            pathname: '/user/[id]',
            params: { id: user.id, name: user.name }
          }}
        >
          {user.name}
        </Link>
      ))}
    </View>
  );
}
Control navigation behavior:
// Replace current screen (no back)
<Link href="/home" replace>
  Go Home
</Link>

// Push to stack (default)
<Link href="/details" push>
  View Details
</Link>

Programmatic Navigation

Use the router object for imperative navigation:
import { router } from 'expo-router';
import { Pressable, Text } from 'react-native';

export default function Home() {
  return (
    <Pressable onPress={() => router.push('/about')}>
      <Text>Go to About</Text>
    </Pressable>
  );
}

router.push()

Navigate forward, adding to history:
import { router } from 'expo-router';

// Navigate to route
router.push('/about');

// With parameters
router.push({
  pathname: '/profile/[id]',
  params: { id: '123' }
});

// With query string
router.push('/profile?id=123&tab=posts');
From the source (imperative-api.tsx:48-50):
/**
 * Navigates to the provided href using a push operation if possible.
 */
push: (href: Href, options?: NavigationOptions) => void;

router.navigate()

Navigate to a route, replacing if already in stack:
import { router } from 'expo-router';

// Navigate to route
router.navigate('/home');

// With parameters
router.navigate({
  pathname: '/user/[id]',
  params: { id: '456' }
});
From the source (imperative-api.tsx:52-54):
/**
 * Navigates to the provided href.
 */
navigate: (href: Href, options?: NavigationOptions) => void;

router.replace()

Replace current screen without adding to history:
import { router } from 'expo-router';

// Replace current screen
router.replace('/login');

// Useful for redirects
router.replace({
  pathname: '/home',
  params: { welcome: 'true' }
});
From the source (imperative-api.tsx:56-62):
/**
 * Navigates to route without appending to the history. Can be used with
 * useFocusEffect to redirect imperatively to a new screen.
 */
replace: (href: Href, options?: NavigationOptions) => void;

router.back()

Go back to previous screen:
import { router } from 'expo-router';

// Go back
router.back();

// Check if can go back first
if (router.canGoBack()) {
  router.back();
} else {
  router.replace('/home');
}
From the source (imperative-api.tsx:40-42):
/**
 * Goes back in the navigation history.
 */
back: () => void;

router.dismiss()

Dismiss screens in a modal or stack:
import { router } from 'expo-router';

// Dismiss one screen
router.dismiss();

// Dismiss multiple screens
router.dismiss(2);

// Dismiss all screens in stack
router.dismissAll();
From the source (imperative-api.tsx:64-76):
/**
 * Navigates to the a stack lower than the current screen using the 
 * provided count if possible, otherwise 1.
 *
 * If the current screen is the only route, it will dismiss the entire stack.
 */
dismiss: (count?: number) => void;

/**
 * Returns to the first screen in the closest stack.
 */
dismissAll: () => void;

router.dismissTo()

Dismiss until reaching a specific route:
import { router } from 'expo-router';

// Dismiss until reaching home
router.dismissTo('/home');

// With parameters
router.dismissTo({
  pathname: '/tabs',
  params: { tab: 'profile' }
});
From the source (imperative-api.tsx:70-72):
/**
 * Dismisses screens until the provided href is reached.
 */
dismissTo: (href: Href, options?: NavigationOptions) => void;

router.canGoBack()

Check if navigation can go back:
import { router } from 'expo-router';

if (router.canGoBack()) {
  router.back();
} else {
  // At root, navigate elsewhere
  router.push('/home');
}

router.canDismiss()

Check if can dismiss current screen:
import { router } from 'expo-router';

if (router.canDismiss()) {
  router.dismiss();
}
From the source (imperative-api.tsx:78-83):
/**
 * Checks if it is possible to dismiss the current screen. Returns true if the
 * router is within the stack with more than one screen in stack's history.
 */
canDismiss: () => boolean;

router.setParams()

Update current route parameters:
import { router } from 'expo-router';

// Update params without navigation
router.setParams({ tab: 'profile', filter: 'recent' });
From the source (imperative-api.tsx:85-87):
/**
 * Updates the current route's query params.
 */
setParams: (params: Partial<RouteInputParams>) => void;

router.prefetch()

Prefetch a screen in the background:
import { router } from 'expo-router';

// Prefetch before navigating
router.prefetch('/profile');

// Later, navigate instantly
router.push('/profile');

useRouter()

Get the router object in a component:
import { useRouter } from 'expo-router';
import { Button } from 'react-native';

export default function Screen() {
  const router = useRouter();
  
  return (
    <Button
      title="Go Back"
      onPress={() => router.back()}
    />
  );
}
From the source (hooks.ts:98-121):
/**
 * Returns the Router object for imperative navigation.
 */
export function useRouter(): Router {
  const { isPreview } = usePreviewInfo();
  if (isPreview) {
    return routerWithWarnings;
  }
  return router;
}

usePathname()

Get current route pathname:
import { usePathname } from 'expo-router';
import { Text } from 'react-native';

export default function Screen() {
  const pathname = usePathname();
  // pathname = "/profile/123" for route /profile/[id]
  
  return <Text>Current path: {pathname}</Text>;
}
From the source (hooks.ts:177-196):
/**
 * Returns the currently selected route location without search parameters.
 * For example, `/acme?foo=bar` returns `/acme`.
 * Segments will be normalized. For example, `/[id]?id=normal` becomes `/normal`.
 */
export function usePathname(): string {
  return useRouteInfo().pathname;
}

useSegments()

Get current route segments:
import { useSegments } from 'expo-router';
import { Text } from 'react-native';

export default function Screen() {
  const segments = useSegments();
  // segments = ["profile", "[id]"] for /profile/123
  
  return <Text>Segments: {segments.join('/')}</Text>;
}
From the source (hooks.ts:132-175):
/**
 * Returns a list of selected file segments for the currently selected route.
 * Segments are not normalized, so they will be the same as the file path.
 * For example, `/[id]?id=normal` becomes `["[id]"]`.
 */
export function useSegments(): RouteSegments;

useLocalSearchParams()

Get route parameters for current screen:
import { useLocalSearchParams } from 'expo-router';
import { Text } from 'react-native';

export default function Profile() {
  const { id, name } = useLocalSearchParams();
  
  return (
    <Text>Profile {id}: {name}</Text>
  );
}
From the source (hooks.ts:244-314):
/**
 * Returns the URL parameters for the contextually focused route.
 * For dynamic routes, both the route parameters and the search 
 * parameters are returned.
 */
export function useLocalSearchParams(): RouteParams;

useGlobalSearchParams()

Get parameters for globally selected route:
import { useGlobalSearchParams } from 'expo-router';
import { Text } from 'react-native';

// Updates even when screen is not focused
export default function Analytics() {
  const params = useGlobalSearchParams();
  
  // Track navigation changes
  React.useEffect(() => {
    trackPage(params);
  }, [params]);
  
  return <Text>Tracking...</Text>;
}
From the source (hooks.ts:198-242):
/**
 * Returns URL parameters for globally selected route, including 
 * dynamic path segments. This function updates even when the route 
 * is not focused.
 */
export function useGlobalSearchParams(): RouteParams;
Configure navigation behavior:
import { router } from 'expo-router';

// With anchor (respects initialRouteName)
router.push('/profile', { withAnchor: true });

// Without anchor
router.push('/profile', { withAnchor: false });
From test file (navigation.test.ios.tsx:77-80):
act(() => router.push('/banana', { withAnchor: true }));
expect(screen.getByTestId('banana')).toBeVisible();
act(() => router.back());
expect(screen.getByTestId('apple')).toBeVisible();

Relative Paths

Use relative navigation:
import { Link } from 'expo-router';

// Navigate to sibling
<Link href="../settings">Settings</Link>

// Navigate to child
<Link href="./details">Details</Link>

// Navigate up
<Link href="..">Back</Link>

External URLs

Navigate to external URLs:
import { Link } from 'expo-router';

// Opens in browser
<Link href="https://expo.dev">
  Visit Expo
</Link>

// Custom scheme
<Link href="mailto:support@example.com">
  Email Support
</Link>

Best Practices

Prefer <Link> for navigation that’s always visible:
// Good: Static link
<Link href="/about">About</Link>

// Less ideal: Button with router
<Button onPress={() => router.push('/about')} />

Use Router for Dynamic Navigation

Use router for conditional or event-driven navigation:
const handleSave = async () => {
  await saveData();
  router.back();
};

const handleAuth = () => {
  if (isLoggedIn) {
    router.replace('/home');
  } else {
    router.replace('/login');
  }
};

Check Before Going Back

Always check if you can go back:
const handleBack = () => {
  if (router.canGoBack()) {
    router.back();
  } else {
    router.replace('/home');
  }
};

Prefetch Important Routes

Prefetch routes users are likely to visit:
import { router } from 'expo-router';
import { useEffect } from 'react';

export default function Home() {
  useEffect(() => {
    // Prefetch likely next screens
    router.prefetch('/profile');
    router.prefetch('/settings');
  }, []);
  
  return <View>...</View>;
}

Common Patterns

Authentication Flow

import { router } from 'expo-router';
import { useAuth } from './auth';

export default function Login() {
  const { login } = useAuth();
  
  const handleLogin = async () => {
    await login(email, password);
    router.replace('/home');
  };
  
  return <Button onPress={handleLogin} title="Login" />;
}
import { router } from 'expo-router';

export default function Profile() {
  return (
    <>
      <Button
        title="Edit Profile"
        onPress={() => router.push('/modal/edit-profile')}
      />
      <Button
        title="Close"
        onPress={() => router.dismiss()}
      />
    </>
  );
}

Conditional Navigation

import { router } from 'expo-router';
import { useEffect } from 'react';
import { useAuth } from './auth';

export default function Protected() {
  const { isAuthenticated } = useAuth();
  
  useEffect(() => {
    if (!isAuthenticated) {
      router.replace('/login');
    }
  }, [isAuthenticated]);
  
  return <View>...</View>;
}

Next Steps

Dynamic Routes

Work with route parameters

Layouts

Share UI across routes

Deep Linking

Configure deep links

Typed Routes

Type-safe navigation