# Reference

## Error handling

Learn how to handle unmatched routes and errors in your app when using Expo Router.

This guide specifies how to handle unmatched routes and errors in your app when using Expo Router.
## Unmatched routes
Native apps don't have a server so there are technically no 404s. However, if you're implementing a router universally, then it makes sense to handle missing routes. This is done automatically for each app, but you can also customize it.
```tsx app/+not-found.tsx
import { Unmatched } from 'expo-router';
export default Unmatched;
```
This will render the default `Unmatched`. You can export any component you want to render instead. We recommend having a link to `/` so users can navigate back to the home screen.
### Route priority
On web, files are served in the following order:
1. Static files in the **public** directory.
2. Standard and dynamic routes in the app directory.
3. [API routes](/router/web/api-routes) in the app directory.
4. Not-found routes will be served last with a 404 status code.
## Error handling
Expo Router enables fine-tuned error handling to enable a more opinionated data-loading strategy in the future.
You can export a nested [`ErrorBoundary`](/versions/latest/sdk/router/#errorboundary) component from any route to intercept and format component-level errors using [React Error Boundaries](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary):
```tsx app/home.tsx
import { View, Text } from 'react-native';
import { type ErrorBoundaryProps } from 'expo-router';
export function ErrorBoundary({ error, retry }: ErrorBoundaryProps) {
  return (
    <View style={{ flex: 1, backgroundColor: "red" }}>
      <Text>{error.message}</Text>
      <Text onPress={retry}>Try Again?</Text>
    </View>
  );
}
export default function Page() { ... }
```
When you export an `ErrorBoundary` the route will be wrapped with a React Error Boundary effectively:
```tsx Virtual
function Route({ ErrorBoundary, Component }) {
  return (
    <Try catch={ErrorBoundary}>
      <Component />
    </Try>
  );
}
```
When `ErrorBoundary` is not present, the error will be thrown to the nearest parent's `ErrorBoundary` and accepts [`error`](/versions/latest/sdk/router/#error) and [`retry`](/versions/latest/sdk/router/#retry) props.
### Work in progress
React Native LogBox needs to be presented less aggressively to develop with errors. Currently, it shows for `console.error` and `console.warn`. However, it should ideally only show for uncaught errors.


## Using URL parameters

Learn how to access and modify route and search parameters in your app.

URL parameters include both **route parameters** and **search parameters**. Expo Router provides hooks for accessing and modifying these parameters.
## Difference between route and search parameters
Route parameters are dynamic segments defined in a URL path, such as `/profile/[user]`, where `user` is a route parameter. They are used to match a route.
Search parameters (also known as query params) are serializable fields that can be appended to a URL, such as `/profile?extra=info`, where `extra` is a search parameter. They are commonly used to pass data between pages.
## Local versus global URL parameters
In nested apps, you'll often have **multiple pages mounted** at the same time. For example, a stack has the previous page and current page in memory when a new route is pushed. Because of this, Expo Router provides two different hooks for accessing URL parameters:
- **useLocalSearchParams**: Returns the URL parameters for the current component. It only updates when the global URL conforms to the route.
- **useGlobalSearchParams**: Returns the global URL regardless of the component. It updates on every URL param change and might cause components to update extraneously in the background.
The hooks `useGlobalSearchParams` and `useLocalSearchParams` allow you to access these parameters within your components, enabling you to retrieve and utilize both types of URL parameters.
Both hooks are typed and accessed the same way. However, the only difference is how frequently they update.
The example below demonstrates the difference between `useLocalSearchParams` and `useGlobalSearchParams`. It uses the following **app** directory structure:
```
âââ app/
    âââ _layout.tsx
    âââ index.tsx
    âââ [user].tsx
```
Step 1: 
    The Root Layout is a stack navigator:
    ```tsx app/_layout.tsx
    import { Stack } from 'expo-router';
    export default function Layout() {
      return <Stack />;
    }
    ```
Step 2: 
    The initial route redirects to the dynamic route **app/[user].tsx**, with **user=evanbacon**:
    ```tsx app/index.tsx
    import { Redirect } from 'expo-router';
    export default function Route() {
      return <Redirect href="/evanbacon" />;
    }
    ```
Step 3: 
    The dynamic route **app/[user]** prints out the global and local URL parameters (route parameters, in this case). It also allows for pushing new instances of the same route with different **route parameters**:
    ```tsx app/[user].tsx
    import { Text, View } from 'react-native';
    import { useLocalSearchParams, useGlobalSearchParams, Link } from 'expo-router';
    const friends = ['charlie', 'james']
    export default function Route() {
      const glob = useGlobalSearchParams();
      const local = useLocalSearchParams();
      console.log("Local:", local.user, "Global:", glob.user);
      return (
        <View>
          <Text>User: {local.user}</Text>
          {friends.map(friend => (
            <Link key={friend} href={`/${friend}`}>
              Visit {friend}
            </Link>
          ))}
        </View>
      );
    }
    ```
Step 4: 
When the app starts, the following log is printed:
```sh
Local: evanbacon Global: evanbacon
```
Pressing "Visit charlie" pushes a new instance of `/[user]` with **user=charlie**, and logs the following:
```sh
Local: charlie Global: charlie
Local: evanbacon Global: charlie
```
Pressing "Visit james" has a similar effect:
```sh
/james" screen
Local: james Global: james
Local: evanbacon Global: james
Local: charlie Global: james
```
    **Results:**
    - `useGlobalSearchParams` made the background screens re-render when the URL **route parameters** changed. It can cause performance issues if overused.
    - Global re-renders are executed in order of the stack, so the first screen is re-rendered first, then the **user=charlie** screen is re-rendered after.
    - `useLocalSearchParams` remained the same, even when the global URL **route parameters** changed. You can leverage this behavior for data fetching to ensure the previous screen's data is still available when you navigate back.
## Statically-typed URL parameters
Both the `useLocalSearchParams` and `useGlobalSearchParams` can be statically typed using a generic. The following is an example for the `user` route parameter:
```tsx app/[user].tsx
import { Text } from 'react-native';
import { useLocalSearchParams } from 'expo-router';
export default function Route() {
  const { user } = useLocalSearchParams<{ user: string }>();
  return <Text>User: {user}</Text>;
}
// Given the URL: `/evanbacon`
// The following is returned: { user: "evanbacon" }
```
Any search parameters (for example, `?query=...`) can be typed optionally:
```tsx app/[user].tsx
const { user, query } = useLocalSearchParams<{ user: string; query?: string }>();
// Given the URL: `/evanbacon?query=hello`
// The following is returned: { user: "evanbacon", query: "hello" }
```
When used with the rest syntax (`...`), route parameters are returned as a string array:
```tsx app/[...everything].tsx
import { Text } from 'react-native';
import { useLocalSearchParams } from 'expo-router';
export default function Route() {
  const { everything } = useLocalSearchParams<{
    everything: string[];
  }>();
  const user = everything[0];
  return <Text>User: {user}</Text>;
}
// Given the URL: `/evanbacon/123`
// The following is returned: { everything: ["evanbacon", "123"] }
```
Any search parameters will continue to be returned as individual strings:
```tsx app/[...everything].tsx
import { Text } from 'react-native';
import { useLocalSearchParams } from 'expo-router';
export default function Route() {
  const { everything } = useLocalSearchParams<{
    everything: string[];
    query?: string;
    query2?: string;
  }>();
  const user = everything[0];
  return <Text>User: {user}</Text>;
}
// Given the URL: `/evanbacon/123?query=hello&query2=world`
// The following is returned: { everything: ["evanbacon", "123"], query: "hello", query2: "world" }
```
## Updating URL parameters
URL parameters can be updated using the **router.setParams** function from the imperative API. Updating a URL parameter will not push anything new to the history stack.
The following example uses a `<TextInput>` to update the search parameter **q**:
```tsx app/search.tsx
import { useLocalSearchParams, router } from 'expo-router';
import { useState } from 'react';
import { TextInput, View } from 'react-native';
export default function Page() {
  const params = useLocalSearchParams<{ query?: string }>();
  const [search, setSearch] = useState(params.query);
  return (
    <TextInput
      value={search}
      onChangeText={search => {
        setSearch(search);
        router.setParams({ query: search });
      }}
      placeholderTextColor="#A0A0A0"
      placeholder="Search"
      style={{
        borderRadius: 12,
        backgroundColor: '#fff',
        fontSize: 24,
        color: '#000',
        margin: 12,
        padding: 16,
      }}
    />
  );
}
```
Here is an example using an `onPress` event to update the route parameter **user**:
```tsx app/[user].tsx
import { useLocalSearchParams, router } from 'expo-router';
import { Text } from 'react-native';
export default function User() {
  const params = useLocalSearchParams<{ user: string }>();
  return (
    <>
      <Text>User: {params.user}</Text>
      <Text onPress={() => router.setParams({ user: 'evan' })}>Go to Evan</Text>
    </>
  );
}
```
## Route parameters versus search parameters
Route parameters are used to match a route, while search parameters are used to pass data between routes. Consider the following structure, where a route parameter is used to match the _user_ route:
```
âââ app/
    âââ index.tsx
    âââ [user].tsx  # "user" is a "route parameter"
```
When the `app/[user]` route is matched, the `user` parameter is passed to the component and never a nullish value. Both search and route parameters can be used together and are accessible with the `useLocalSearchParams` and `useGlobalSearchParams` hooks:
```tsx app/[user].tsx
import { useLocalSearchParams } from 'expo-router';
export default function User() {
  const {
    // The route parameter
    user,
    // An optional search parameter.
    tab,
  } = useLocalSearchParams<{ user: string; tab?: string }>();
  console.log({ user, tab });
  // Given the URL: `/bacon?tab=projects`, the following is printed:
  // { user: 'bacon', tab: 'projects' }
  // Given the URL: `/expo`, the following is printed:
  // { user: 'expo', tab: undefined }
}
```
Whenever a route parameter is changed, the component will re-mount.
```tsx app/[user].tsx
import { Text } from 'react-native';
import { router, useLocalSearchParams, Link } from 'expo-router';
export default function User() {
  // All three of these will change the route parameter `user`, and add a new user page.
  return (
    <>
      <Text onPress={() => router.setParams({ user: 'evan' })}>Go to Evan</Text>
      <Text onPress={() => router.push('/mark')}>Go to Mark</Text>
      <Link href="/charlie">Go to Charlie</Link>
    </>
  );
}
```
{/*
TODO: REMOVE COMMENT WHEN https://github.com/expo/expo/pull/30268 IS PUBLISHED
## Array support
URL parameters that are present multiple times will be grouped together as an array.
```tsx app/hash.tsx
import { router, useLocalSearchParams } from 'expo-router';
export default function Route() {
  // If the current URL is `/route?myParam=1&myParam=2
  const { myParam } = useLocalSearchParams();
  // myParam === ["1", "2"]
}
```
\*/}
## Hash support
The URL [hash](https://developer.mozilla.org/en-US/docs/Web/API/URL/hash) is a string that follows the `#` symbol in a URL. It is commonly used on websites to link to a specific section of a page, but it can also be used to store data. Expo Router treats the hash as a special search parameter using the name `#`. It can be accessed and modified using the same hooks and APIs from [search parameters](#local-versus-global-search-parameters).
```tsx app/hash.tsx
import { Text } from 'react-native';
import { router, useLocalSearchParams, Link } from 'expo-router';
export default function User() {
  // Access the hash
  const { '#': hash } = useLocalSearchParams<{ '#': string }>();
  return (
    <>
      <Text onPress={() => router.setParams({ '#': 'my-hash' })}>Set a new hash</Text>
      <Text onPress={() => router.push('/#my-hash')}>Push with a new hash</Text>
      <Link href="/#my-hash">Link with a hash</Link>
    </>
  );
}
```
## Reserved parameters
Certain URL parameters are reserved for internal use by Expo Router and React Navigation. Avoid using the following names for your own parameters to prevent conflicts:
- `screen`
- `params`
- `initial`
- `state`


## Color

Access platform-specific colors with type safety in Expo Router.

The `Color` API provides type-safe access to platform-specific colors on iOS and Android. It wraps React Native's `PlatformColor` with full TypeScript support, enabling autocomplete and compile-time type checking for system colors.
## Usage
```tsx
import { Color } from 'expo-router';
```
The `Color` object has two platform-specific namespaces:
- `Color.ios.*` - iOS system colors from UIKit
- `Color.android.*` - Android colors including base colors, attributes, and Material Design 3 colors
## iOS colors
Access iOS system colors through `Color.ios.*`. These map directly to UIKit's [standard colors](https://developer.apple.com/documentation/uikit/standard-colors) and [UI element colors](https://developer.apple.com/documentation/uikit/ui-element-colors).
```tsx
import { Color } from 'expo-router';
import { View, Text } from 'react-native';
function MyComponent() {
  return (
    <View style={{ backgroundColor: Color.ios.systemBackground }}>
      <Text style={{ color: Color.ios.label }}>Hello, World!</Text>
    </View>
  );
}
```
iOS colors automatically adapt to the system appearance (light/dark mode) and accessibility settings.
## Android colors
Android provides four categories of colors through the `Color.android` namespace.
### Base colors
Access Android system colors through `Color.android.*`. These map to `@android:color/` resources.
```tsx
import { Color } from 'expo-router';
// Basic colors
Color.android.black;
Color.android.white;
Color.android.transparent;
// Background colors
Color.android.background_dark;
Color.android.background_light;
```
See the [Android R.color documentation](https://developer.android.com/reference/android/R.color) for the full list of available colors.
### Attribute colors
Access Android theme attributes through `Color.android.attr.*`. These resolve colors from the current theme using `?attr/` syntax.
```tsx
import { Color } from 'expo-router';
// Theme colors
Color.android.attr.colorPrimary;
Color.android.attr.colorSecondary;
Color.android.attr.colorAccent;
Color.android.attr.colorBackground;
```
See the [Android R.attr documentation](https://developer.android.com/reference/android/R.attr) for more information.
### Material Design 3 static colors
Access Material Design 3 static colors through `Color.android.material.*`. These use the standard Material 3 Light/Dark theme colors.
```tsx
import { Color } from 'expo-router';
// Primary colors
Color.android.material.primary;
Color.android.material.onPrimary;
Color.android.material.primaryContainer;
Color.android.material.onPrimaryContainer;
// Surface colors
Color.android.material.surface;
Color.android.material.onSurface;
```
See the [Material Design 3 color roles documentation](https://m3.material.io/styles/color/roles) for more information about each color role.
### Material Design 3 dynamic colors
Access Material Design 3 dynamic colors through `Color.android.dynamic.*`. Dynamic colors adapt to the user's wallpaper using Android's [Dynamic Color feature](https://m3.material.io/styles/color/dynamic/user-generated-source), available on Android 12+ (API 31+).
```tsx
import { Color } from 'expo-router';
// Dynamic colors adapt to user's wallpaper
Color.android.dynamic.primary;
Color.android.dynamic.onPrimary;
Color.android.dynamic.surface;
Color.android.dynamic.onSurface;
```
The available colors are the same as [Material 3 static colors](#material-design-3-static-colors).
### Responding to theme changes on Android
Android Material colors (both static and dynamic) respond to the system's light/dark mode. To ensure your component re-renders when the theme changes, use the `useColorScheme()` hook from React Native.
```tsx
import { Color } from 'expo-router';
import { View, Text, useColorScheme } from 'react-native';
function MyComponent() {
  // Triggers re-render when system theme changes
  useColorScheme();
  return (
    <View style={{ backgroundColor: Color.android.dynamic.surface }}>
      <Text style={{ color: Color.android.dynamic.onSurface }}>Hello, World!</Text>
    </View>
  );
}
```
Without using `useColorScheme()`, the colors may not update when the user switches between light and dark mode.
## Cross-platform usage
The `Color` API is platform-specific. Use `useMemo` to select the appropriate color for each platform:
```tsx
import { Platform, View, Text } from 'react-native';
import { Color } from 'expo-router';
function MyComponent() {
  const backgroundColor = Platform.select({
    ios: Color.ios.systemBackground,
    android: Color.android.dynamic.surface,
    default: '#000000',
  });
  const textColor = Platform.select({
    ios: Color.ios.label,
    android: Color.android.dynamic.onSurface,
    default: '#FFFFFF',
  });
  return (
    <View style={{ backgroundColor }}>
      <Text style={{ color: textColor }}>Hello, World!</Text>
    </View>
  );
}
```
## API reference
For the full list of available types and colors, see the [Color API reference](/versions/latest/sdk/router/#color).


## Sitemap

Learn how to use the sitemap to debug your app with Expo Router.

On native, you can use the [`uri-scheme`](https://www.npmjs.com/package/uri-scheme) CLI to test opening native links on a device.
For example, if you want to launch the Expo Go app on iOS to the `/form-sheet` route, run:
```sh
$ npx uri-scheme open exp://192.168.87.39:19000/--/form-sheet --ios
```
> Replace `192.168.87.39:19000` with the IP address shown when running `npx expo start`.
You can also search for links directly in a browser like Safari or Chrome to test deep linking on physical devices. Learn more about [testing deep links](https://reactnavigation.org/docs/deep-linking).
## Sitemap
Expo Router currently injects a **/\_sitemap** automatically that provides a list of all routes in the app. This is useful for debugging.
The sitemap can be removed by adding `sitemap: false` to the `expo-router` config plugin in the app config:
```json app.json
{
  "plugins": [
    [
      "expo-router",
      {
        "sitemap": false
      }
    ]
  ]
}
```


## Redirects

Learn how to redirect URLs in Expo Router.

You can redirect a request to a different URL based on some in-app criteria. Expo Router supports a number of different redirection patterns.
## Using `Redirect` component
You can immediately redirect from a particular screen by using the `Redirect` component:
```tsx
import { View, Text } from 'react-native';
import { Redirect } from 'expo-router';
export default function Page() {
  const { user } = useAuth();
  if (!user) {
    return <Redirect href="/login" />;
  }
  return (
    <View>
      <Text>Welcome Back!</Text>
    </View>
  );
}
```
## Using `useRouter` hook
You can also redirect imperatively with the `useRouter` hook:
```tsx
import { Text } from 'react-native';
import { useRouter, useFocusEffect } from 'expo-router';
function MyScreen() {
  const router = useRouter();
  useFocusEffect(() => {
    // Call the replace method to redirect to a new route without adding to the history.
    // We do this in a useFocusEffect to ensure the redirect happens every time the screen
    // is focused.
    router.replace('/profile/settings');
  });
  return <Text>My Screen</Text>;
}
```


## Link preview

Learn how to add a preview to your link on iOS when using Expo Router.

> **important** Link preview is an iOS-only feature available in SDK 54 and later.
Link preview (also known as "Peek and Pop") is a feature commonly used on iOS to show users a preview popup of the screen for a link.
This guide will show you how to add and customize a link preview for your app on iOS.
If you have a link in your app, you can add a link preview to it by replacing the link's content with a [`Link.Trigger`](/versions/latest/sdk/router/#linktrigger) and adding the [`Link.Preview`](/versions/latest/sdk/router/#linkpreview) component to it. This will create a preview of the page that the link points to.
```tsx
import { Link } from 'expo-router';
export default function Page() {
  return (
    <Link href="/about">
      <Link.Trigger>About</Link.Trigger>
      <Link.Preview />
    </Link>
  );
}
```
## Customizing the link preview
By default, the link preview is rendered as a full-sized page snapshot. There are several ways to customize this behavior.
### Custom size
You can use `width` and `height` to suggest a preferred preview size. The system will consider these preferences but may override them based on available space or platform behavior.
```tsx
<Link href="...">
  <Link.Trigger>Content</Link.Trigger>
  <Link.Preview style={{ width: 300, height: 200 }} />
</Link>
```
The following example shows a custom link preview size on iOS:
### Custom preview
If you don't want to show the default preview, you can pass custom content to the `Link.Preview` component via children. This custom content will replace the default preview of the link target.
```tsx
export default function Page() {
  const [imageSize, setImageSize] = useState({ width: 0, height: 0 });
  const { width } = useWindowDimensions();
  const previewHeight = (width / imageSize.width) * imageSize.height;
  return (
    <Link href="/about">
      <Link.Trigger>About</Link.Trigger>
      <Link.Trigger>Content</Link.Trigger>
      <Link.Preview style={{ width, height: previewHeight }}>
        <Image
          onLoad={e => setImageSize(e.nativeEvent.source)}
          source={source}
          style={{ width: '100%', height: '100%' }}
        />
      </Link.Preview>
    </Link>
  );
}
```
The following example shows a custom link preview on iOS:
## Menu
To render a context menu next to the preview, add a [`Link.Menu`](/versions/latest/sdk/router/#linkmenu) with [`Link.MenuAction`](/versions/latest/sdk/router/#linkmenuaction) children.
```tsx
<Link href="/about">
  <Link.Trigger>About</Link.Trigger>
  <Link.Menu>
    <Link.MenuAction title="Share" icon="square.and.arrow.up" onPress={handleSharePress} />
    <Link.MenuAction title="Block" icon="nosign" destructive onPress={handleBlockPress} />
  </Link.Menu>
</Link>
```
The following example shows a custom link preview on iOS:
### Icons
You can specify an icon for each menu action using [SF Symbols](https://developer.apple.com/sf-symbols/).
```tsx
<Link href="/about">
  <Link.Trigger>About</Link.Trigger>
  <Link.Menu>
    <Link.MenuAction title="Share" icon="square.and.arrow.up" onPress={handleSharePress} />
    <Link.MenuAction title="Block" icon="nosign" onPress={handleBlockPress} />
    <Link.MenuAction
      title="Follow"
      icon="person.crop.circle.badge.plus"
      onPress={handleFollowPress}
    />
    <Link.MenuAction title="Copy" icon="doc.on.doc" onPress={handleCopyPress} />
  </Link.Menu>
</Link>
```
The following example shows a context menu with four elements, each using a different icon on iOS:
### Nested menus
You can nest menus by placing a [`Link.Menu`](/versions/latest/sdk/router/#linkmenu) inside another menu:
```jsx
<Link href="...">
  <Link.Trigger>About</Link.Trigger>
  <Link.Menu>
    <Link.MenuAction title="Share" icon="square.and.arrow.up" onPress={() => {}} />
    <Link.Menu title="More" icon="ellipsis">
      <Link.MenuAction title="Copy" icon="doc.on.doc" onPress={() => {}} />
      <Link.MenuAction title="Delete" icon="trash" destructive onPress={() => {}} />
    </Link.Menu>
  </Link.Menu>
</Link>
```
The following example shows a nested context menu on iOS:
### More customization options
To explore all available customization options, see the API documentation for [`Link.MenuAction`](/versions/latest/sdk/router/#linkmenuaction).
## Detecting if component is in preview
If you're building a component that might be rendered inside a preview, you can use the [`useIsPreview()`](/versions/latest/sdk/router/#useispreview) hook to adjust its behavior accordingly:
```jsx
function MyComponent() {
  // This will be true if component/screen is being rendered inside a preview
  const isInsidePreview = useIsPreview();
  return isInsidePreview ? <Text>From within preview</Text> : <Text>I am outside of preview</Text>;
}
```
## Known limitations
### `replace` not supported
Using link previews with [`replace`](/versions/latest/sdk/router/#replace) mode is currently **not supported**. Previews can only be used with the default [`push`](/versions/latest/sdk/router/#push) navigation mode.
### JavaScript tabs and slots
When navigating within JavaScript tabs (rather than native tabs) or [`Slot`](/versions/latest/sdk/router/#slot), preview transition animations may appear clunky. This is due to React's delayed rendering while the native preview animation begins immediately. To prevent this issues use, native tabs and stack navigators.
### Missing `Link.Trigger`
If you render a `Link` with a preview or context menu, but without a `Link.Trigger`, an exception will be thrown. The same applies if you place any non-`Link.*` component directly inside `Link` when using preview mode.
### Multiple `Link.Trigger` children with `asChild` prop
When using `Link` with `asChild`, you may only specify **one** child for `Link.Trigger`. The `onPress` event will be forwarded to that child only.
### Changing href while preview is open
Changing `href` prop's path dynamically while the preview is open is **not supported**. You may only modify the query parameters dynamically.


## Typed routes

Learn how to use statically typed links and routes in Expo Router.

> Available when using TypeScript in your project. Expo Router supports standard TypeScript out of the box. See the [TypeScript](/guides/typescript/) guide for more information on how to set it up.
Expo Router supports generating TypeScript types automatically with Expo CLI. This enables `<Link>`, and the [hooks API](/router/reference/hooks) to be statically typed. This feature is currently in beta and is not enabled by default.
## Get started
### Quick start
If you created your project using the [Expo Router quick start guide](/router/installation/#quick-start) then your project is already configured to use typed routes. The Expo CLI will generate the required type file the first time you run `npx expo start`. Then, you can use autocomplete for `href` props whenever you use an Expo Router `<Link>` component in a **.tsx** file.
### Manual configuration
While the feature is in beta, you can enable it by setting `experiments.typedRoutes` to `true` in your **app.json**:
```json app.json
{
  "expo": {
    "experiments": {
      "typedRoutes": true
    }
  }
}
```
Run `npx expo customize tsconfig.json` to configure your **tsconfig.json** to add the required `includes` fields.
Then, start the development server by running `npx expo start`. You can now use autocomplete in the Expo Router `<Link>` component's `href` prop.
## Type generation
Typed routes in Expo Router are automatically generated when the development server starts. By default, these generated types are configured to be untracked by Git and will be added to the local **.gitignore** file. This ensures that autogenerated files do not clutter your version control system.
If you find yourself in a situation where you need to generate these types without initiating the development server, such as during type checking on a Continuous Integration (CI) server. To do this, run the command `npx expo customize tsconfig.json` on the CI.
## Statically typed routes
Components and functions that use `Href<T>` will now be statically typed and have a much stricter definition. For example:
```tsx
â <Link href="/about" />
â <Link href="/user/1" />
â <Link href={`/user/${id}`} />
â <Link href={("/user" + id) as Href} />
// TypeScript errors if href is not a valid route
â <Link href="/usser/1" />
```
> **Note**: A [`Route` type](/versions/latest/sdk/router/#route) is also made available by `expo-router`, which matches all valid routes in a project automatically.
For dynamic routes, Href's need to be objects and their parameters are strictly typed:
```tsx
â <Link href={{ pathname: "/user/[id]", params: { id: 1 }}} />
// TypeScript errors as href is valid, but it should be a HrefObject with params
â <Link href="/user/[id]" />
// TypeScript errors as params contain invalid keys
â <Link href={{ pathname: "/user/[id]", params: { _id: 1 }}} />
// TypeScript errors as params contain unknown keys
â <Link href={{ pathname: "/user/[id]", params: { id: 1, id2: 2 }}} />
```
### Relative paths
Statically typed routes do not support relative paths. You'll need to use absolute paths for all routes:
```tsx
â <Link href="/about" />
// Relative paths are not supported
â <Link href="./about" />
```
You can leverage the `useSegments()` hooks from `expo-router` to create complex relative paths. Consider the following structure:
```
âââ app/
â   âââ (feed)/
â   â   âââ _layout.tsx
â   â   âââ feed.tsx
â   â   âââ search.tsx
â   â   âââ profile.tsx
â   âââ (search)/
â       âââ profile.tsx
âââ components/
    âââ button.tsx
```
You can ensure that you push to the same tab by using the `useSegments()` hook to get the first segment of the current route.
```tsx button.tsx
import { Link, useSegments } from 'expo-router';
export function Button() {
  const [
    // This will be either `(feed)` or `(search)` depending on the current tab.
    first,
  ] = useSegments();
  return <Link href={`/${first}/profile`}>Push profile</Link>;
}
```
Now, you can leverage `<Button />` from both **app/(feed)/feed.tsx** and **app/(search)/search.tsx** to push `./profile` while preserving the current tab.
If you require the segments for a specific use, you can pass the full route to `useSegments`:
```tsx button.tsx
import { Link, useSegments } from 'expo-router';
export function useMySegments() {
  const segments = useSegments<'/(search)/profile'>();
  //    ^? segments = ['(search)', 'profile']
  return segments;
}
```
## Imperative navigation
You can use the typed `router` object to navigate imperatively:
```tsx
import { router } from 'expo-router';
router.push('/about');
```
Or with the typed `useRouter()` hook:
```tsx
import { useRouter } from 'expo-router';
function Page() {
  const router = useRouter();
  router.push('/about');
  // ...
}
```
## Route parameters
For strongly typed route parameters, you can pass a full href to the `useLocalSearchParams` and `useGlobalSearchParams` hooks. For example:
```tsx app/search.tsx
import { Text } from 'react-native';
import { useLocalSearchParams } from 'expo-router';
export default function Page() {
  const {
    profile, // string
    search, // string[]
  } = useLocalSearchParams<'/(search)/[profile]/[...search]'>();
  return (
    <>
      <Text>Profile: {profile}</Text>
      <Text>Search: {search.join(',')}</Text>
    </>
  );
}
```
## Query parameters
Most query parameters will not be represented in the file system and therefore cannot be typed automatically. You can type query parameters manually by passing a generic to the `useLocalSearchParams` and `useGlobalSearchParams` hooks. For example:
```tsx app/search.tsx
import { Text } from 'react-native';
import { useLocalSearchParams } from 'expo-router';
export default function Page() {
  const { query } = useLocalSearchParams<{ query?: string }>();
  return <Text>Search: {query ?? 'unset'}</Text>;
}
```
If you need a combination of route and query parameters, pass the route as the first generic and then the query parameters:
```tsx app/search.tsx
import { Text } from 'react-native';
import { useLocalSearchParams } from 'expo-router';
export default function Page() {
  const { query, profile, search } = useLocalSearchParams<
    '/[profile]/[...search]',
    { query?: string }
  >();
  return <Text>Search: {query ?? 'unset'}</Text>;
}
```
## Changes made to the environment
When typed routes is enabled, Expo CLI will generate a git ignored **expo-env.d.ts** file in your project's root directory, update the **.gitignore** to ignore the new root **expo-env.d.ts** file, and modify the **tsconfig.json** to include the new **expo-env.d.ts** file.
The `includes` field in your **tsconfig.json** gets updated to include **expo-env.d.ts** and a hidden **.expo** directory. These entries are required and should not be removed from the file.
The generated **expo-env.d.ts** should not be removed or changed at any time. It should not be committed and should be ignored by version control.
### Global types
Expo CLI will add the following global types to your project when typed routes is enabled:
- Sets `process.env.NODE_ENV = "development" | "production" | "test"`
- Allows the importing of `.[css|sass|scss]` files
- Sets the exports of `*.module.[css|sass|scss]` to be `Record<string, string>`
- Add types for Metro's `require.context`. This is enabled by `expo/metro-config` and used for static route generation.
### React Native Web
With typed routes enabled, Expo CLI also augments the `react-native` types to support React Native Web. The following changes are made:
- Add additional web-only styles for `ViewStyle`, `TextStyle`, `ImageStyle`
- Add `tabIndex`, `aria-level`, `lang` to `TextProps`
- Add `hovered` to Pressable's `children` and `style` state callback function
- Add `className` elements


## Screen tracking for analytics

Learn how to enable screen tracking for analytic when using Expo Router.

Unlike React Navigation, Expo Router always has access to a URL. This means screen tracking is as easy as the web.
1. Create a higher-order component that observes the currently selected URL
2. Track the URL in your analytics provider
```
âââ app/
    âââ _layout.tsx
```
```tsx app/_layout.tsx
import { useEffect } from 'react';
import { usePathname, useGlobalSearchParams, Slot } from 'expo-router';
export default function Layout() {
  const pathname = usePathname();
  const params = useGlobalSearchParams();
  // Track the location in your analytics provider here.
  useEffect(() => {
    analytics.track({ pathname, params });
  }, [pathname, params]);
  // Export all the children routes in the most basic way.
  return <Slot />;
}
```
Now when the user changes routes, the analytics provider will be notified.
## Migrating from React Navigation
React Navigation's [screen tracking guide](https://reactnavigation.org/docs/screen-tracking/) cannot make the same assumptions about the navigation state that Expo Router can. As a result, the implementation requires the use of `onReady` and `onStateChange` callbacks. Avoid using these methods if possible as the root `<NavigationContainer />` is not directly exposed and allows cascading in Expo Router.


## Top-level src directory

Learn how to use a top-level src directory in your Expo Router project.

As your project grows, it can be helpful to move all the directories containing application code into a single **src** directory. Expo Router supports this out of the box.
## Using a top-level src directory
Step 1: 
Move your **app** directory to **src/app**.
```
âââ src/
â   âââ app/
â   â   âââ _layout.tsx
â   â   âââ index.tsx
â   âââ components/
â       âââ button.tsx
âââ package.json
```
Step 2: 
Update [TypeScript path aliases](/guides/typescript#path-aliases) in the **tsconfig.json** file to point to the **src** directory instead of the root directory. If you use the default `@/*` alias, set it to **./src/\***:
```json tsconfig.json
{
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}
```
This keeps `@/` imports working after moving your app directory into **src**.
Step 3: 
Restart your development server.
```sh
$ npx expo start
$ npx expo export
```
### Notes
- The config files (**app.config.ts**, **app.json**, **package.json**, **metro.config.js**, **tsconfig.json**) should remain in the root directory.
- The **src/app** directory takes higher precedence than the root **app** directory. Only the **src/app** directory will be used if you have both.
- The **public** directory should remain in the root directory.
- Static rendering will automatically use the **src/app** directory if it exists.
- You may consider updating any [type aliases](/guides/typescript#path-aliases) to point to the **src** directory instead of the root directory.
## Custom directory
> **warning** Changing the default root directory is highly discouraged. We will not accept bug reports regarding projects with custom root directories.
You can dangerously customize the root directory using the Expo Router Config Plugin. The following will change the root directory to **src/routes**, relative to the project root.
```json app.json
{
  "plugins": [
    [
      "expo-router",
      {
        "root": "./src/routes"
      }
    ]
  ]
}
```
This may lead to unexpected behavior. Many tools assume the root directory to be either **app** or **src/app**. Only tools in the exact version of Expo CLI will respect the config plugin.


## Testing configuration for Expo Router

Learn how to create integration tests for your app when using Expo Router.

Expo Router relies on your file system, which can present challenges when setting up mocks for integration tests. Expo Router's submodule, `expo-router/testing-library`, is a set of testing utilities built on top of the popular [`@testing-library/react-native`](https://callstack.github.io/react-native-testing-library/) and allows you to quickly create in-memory Expo Router apps that are pre-configured for testing.
## Configuration
Before you proceed, ensure you have set up `jest-expo` according to the [Unit testing with Jest](/develop/unit-testing/) and [`@testing-library/react-native`](https://callstack.github.io/react-native-testing-library/docs/start/quick-start) in your project.
> **Note**: When using Expo Router, do not put your test files inside the **app** directory. All files inside your **app** directory must be either routes or layout files. Instead, use the **\_\_tests\_\_** directory or a separate directory. This approach is explained in [Unit testing with Jest](/develop/unit-testing/#structure-your-tests).
## `renderRouter`
`renderRouter` extends the functionality of [`render`](https://callstack.github.io/react-native-testing-library/docs/api#render) to simplify testing with Expo Router. It returns the same query object as [`render`](https://callstack.github.io/react-native-testing-library/docs/api#render), and is compatible with [`screen`](https://callstack.github.io/react-native-testing-library/docs/api#screen), allowing you to use the standard [query API](https://callstack.github.io/react-native-testing-library/docs/api/queries) to locate components.
`renderRouter` accepts the same [options](https://callstack.github.io/react-native-testing-library/docs/api#render-options) as `render` and introduces an additional option `initialUrl`, which sets an initial route for simulating deep-linking.
#### Inline file system
`renderRouter(mock: Record<string, ReactComponent>, options: RenderOptions)`
`renderRouter` can provide inline-mocking of a file system by passing an object to this function as the first parameter. The keys of the object are the mock filesystem paths. **Do not use leading relative (`./`) or absolute (`/`) notation when defining these paths and exclude file extension.**
```tsx app.test.tsx
import { renderRouter, screen } from 'expo-router/testing-library';
import { View } from 'react-native';
it('my-test', async () => {
  const MockComponent = jest.fn(() => <View />);
  renderRouter(
    {
      index: MockComponent,
      'directory/a': MockComponent,
      '(group)/b': MockComponent,
    },
    {
      initialUrl: '/directory/a',
    }
  );
  expect(screen).toHavePathname('/directory/a');
});
```
#### Inline file system with `null` components
`renderRouter(mock: string[], options: RenderOptions)`
Providing an array of strings to `renderRouter` will create an inline mock filesystem with `null` components (`{ default: () => null }`). This is useful for testing scenarios where you do not need to test the output of a route.
```tsx app.test.tsx
import { renderRouter, screen } from 'expo-router/testing-library';
it('my-test', async () => {
  renderRouter(['index', 'directory/a', '(group)/b'], {
    initialUrl: '/directory/a',
  });
  expect(screen).toHavePathname('/directory/a');
});
```
#### Path to fixture
`renderRouter(fixturePath: string, options: RenderOptions)`
`renderRouter` can accept a directory path to mock an existing fixture. Ensure that the provided path is relative to the current test file.
```tsx app.test.tsx
import { renderRouter } from 'expo-router/testing-library';
import { View } from 'react-native';
it('my-test', async () => {
  const MockComponent = jest.fn(() => <View />);
  renderRouter('./my-test-fixture');
});
```
#### Path to the fixture with overrides
`renderRouter({ appDir: string, overrides: Record<string, ReactComponent>}, options: RenderOptions)`
For more intricate testing scenarios, `renderRouter` can leverage both directory path and inline-mocking methods simultaneously. The `appDir` parameter takes a string representing a pathname to a directory. The overrides parameter is an inline mock that can be used to override specific paths within the `appDir`. This combination allows for fine-tuned control over the mock environment.
```tsx app.test.tsx
import { renderRouter } from 'expo-router/testing-library';
import { View } from 'react-native';
it('my-test', async () => {
  const MockAuthLayout = jest.fn(() => <View />);
  renderRouter({
    appDir: './my-test-fixture',
    overrides: {
      'directory/(auth)/_layout': MockAuthLayout,
    },
  });
});
```
## Jest matchers
The following matches have been added to `expect` and can be used to assert values on `screen`.
#### toHavePathname()
Assert the current pathname against a given string. The matcher uses the value of the [`usePathname`](/router/reference/hooks/#usepathname) hook on the current `screen`.
```tsx app.test.tsx
expect(screen).toHavePathname('/my-router');
```
#### toHavePathnameWithParams()
Assert the current pathname, including URL parameters, against a given string. This is useful to assert the appearance of URL in a web browser.
```tsx app.test.tsx
expect(screen).toHavePathnameWithParams('/my-router?hello=world');
```
#### toHaveSegments()
Assert the current segments against an array of strings. The matcher uses the value of the [`useSegments`](/router/reference/hooks/#usesegments) hook on the current `screen`.
```tsx app.test.tsx
expect(screen).toHaveSegments(['[id]']);
```
#### useLocalSearchParams()
Assert the current local URL parameters against an object. The matcher uses the value of the [`useLocalSearchParams`](/router/reference/hooks/#uselocalsearchparams) hook on the current `screen`.
```tsx app.test.tsx
expect(screen).useLocalSearchParams({ first: 'abc' });
```
#### useGlobalSearchParams()
Assert the current screen's pathname that matches a value. Compares using the value of [`useGlobalSearchParams`](/router/reference/hooks/#useglobalsearchparams) hook.
Assert the current global URL parameters against an object. The matcher uses the value of the [`useGlobalSearchParams`](/router/reference/hooks/#useglobalsearchparams) hook on the current `screen`.
```tsx app.test.tsx
expect(screen).useGlobalSearchParams({ first: 'abc' });
```
#### toHaveRouterState()
An advanced matcher that asserts the current router state against an object.
```tsx app.test.tsx
expect(screen).toHaveRouterState({
  routes: [{ name: 'index', path: '/' }],
});
```


## Troubleshooting

Fixing common issues with Expo Router setup.

## Missing files or source maps in React Native DevTools
This can happen if your Chrome DevTools has exclusions within its ignore list. To fix the issue, use [React Native DevTools](https://reactnative.dev/docs/react-native-devtools):
1. Start the React Native DevTools by pressing <kbd>j</kbd> from the development server running in the terminal window
2. Open **Settings** by clicking the gear icon
3. Under **Extensions**, click **Restore defaults and reload**
4. Open **Settings** again, and go to **Ignore List** tab
5. Uncheck any exclusions for `/node_modules/`
## `EXPO_ROUTER_APP_ROOT` not defined
If `process.env.EXPO_ROUTER_APP_ROOT` is not defined you'll see the following error:
```sh
$ Invalid call at line 11: process.env.EXPO_ROUTER_APP_ROOT First argument of require.context should be a string.
```
This can happen when the Babel plugin `expo-router/babel` is not used in the project **babel.config.js**. You can try clearing the cache with:
```sh
$ npx expo start --clear
```
Alternatively, you can circumvent this issue by creating an **index.js** file in the root of your project with the following contents:
```jsx index.js
import { registerRootComponent } from 'expo';
import { ExpoRoot } from 'expo-router';
// Must be exported or Fast Refresh won't update the context
export function App() {
  const ctx = require.context('./app');
  return <ExpoRoot context={ctx} />;
}
registerRootComponent(App);
```
Then, update your app's main entry point in **package.json**:
```json package.json
{
  "main": "index.js"
}
```
> Do not use this to change the root directory (**app**) as it won't account for usage in any other places.
## `require.context` not enabled
This can happen when using a custom version of `@expo/metro-config` that does not enable context modules. Expo Router requires the project **metro.config.js** to use `expo-router/metro` as the default configuration. Delete the **metro.config.js**, or extend `expo/metro-config`. See [Customizing metro](/guides/customizing-metro/) for more information.
## Missing back button
If you set up a modal or another screen that is expected to have a back button, then you'll need to add [`unstable_settings`](/router/advanced/router-settings/) to the route's layout to ensure the initial route is configured. Initial routes are somewhat unique to mobile apps and fit awkwardly in the system &mdash; improvements pending.
```tsx app/_layout.tsx
export const unstable_settings = {
  initialRouteName: 'index',
};
```