Edit this page
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 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 to be statically typed. This feature is currently in beta and is not enabled by default.
If you created your project using the Expo Router quick start guide 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.
While the feature is in beta, you can enable it by setting experiments.typedRoutes
to true
in your 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.
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.
Components and functions that use Href<T>
will now be statically typed and have a much stricter definition. For example:
✅ <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" />
For dynamic routes, Href's need to be objects and their parameters are strictly typed:
✅ <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 }}} />
Statically typed routes do not support relative paths. You'll need to use absolute paths for all routes:
✅ <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.
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 your, you can pass the full route to useSegments
import { Link, useSegments } from 'expo-router';
export function useMySegments() {
const segments = useSegments<'app/(search)/profile.tsx'>();
// ^? segments = ['app', '(search)', 'profile']
return segments;
}
You can use the typed router
object to navigate imperatively:
import { router } from 'expo-router';
router.push('/about');
Or with the typed useRouter()
hook:
import { useRouter } from 'expo-router';
function Page() {
const router = useRouter();
router.push('/about');
// ...
}
For strongly typed route parameters you can pass a full href to the useLocalSearchParams
and useGlobalSearchParams
hooks
import { Text } from 'react-native';
import { useLocalSearchParams } from 'expo-router';
export default function Page() {
const {
profile, // string
search, // string[]
} = useLocalSearchParams<'app/(search)/[profile]/[...search].tsx'>();
return (
<>
<Text>Profile: {profile}</Text>
<Text>Search: {search.join(',')}</Text>
</>
);
}
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:
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
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>;
}
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.
Expo CLI will add the following global types to your project when typed routes is enabled:
process.env.NODE_ENV = "development" | "production" | "test"
.[css|sass|scss]
files*.module.[css|sass|scss]
to be Record<string, string>
require.context
. This is enabled by expo/metro-config
and used for static route generation.With typed routes enabled, Expo CLI also augments the react-native
types to support React Native Web. The following changes are made:
ViewStyle
, TextStyle
, ImageStyle
tabIndex
, aria-level
, lang
to TextProps
hovered
to Pressable's children
and style
state callback functionclassName
elements