Enabling and Using Expo Router in SDK 50+: A Practical Guide
Enable Expo Router in SDK 50+ with a single install, configure file‑based routes, and add type‑safe navigation. Learn how to set up typed routes, dynamic segments, deep links, and web export, plus common pitfalls to avoid.
10 Sept 2025, 04:49 UTC

Why Use Expo Router?
Expo Router brings file‑based routing, layout composition, and type‑safe navigation to the managed workflow. It replaces the manual stack and tab setup of React Navigation with a declarative file system that is easier to maintain, especially for large apps that grow over time.
Getting Started: Enable and Configure
Start by installing the router and wiring the entry point.
# In the project root
expo install expo-router
# Add the router entry to package.json
{
"name": "my-app",
"main": "expo-router/entry"
}
Next, add the typedRoutes experiment if you want compile‑time safety (requires TypeScript 5.3+).
{
"expo": {
"plugins": {
"expo-router": {
"experiments": {
"typedRoutes": true
}
}
}
}
}
Run npx expo customize tsconfig.json to extend the TypeScript config for the router.
File‑Based Routing Basics
All route files live under the app directory. The file name (minus the extension) becomes the path segment. For example:
app/index.tsx→/app/settings.tsx→/settingsapp/user/[id].tsx→/user/:id
Non‑route files must be placed outside app (e.g., components/) to avoid accidental routing.
Typed Routes and Navigation
With typedRoutes enabled, the router generates a Routes type you can import. Navigation becomes fully type‑checked.
import { Link, useRouter } from "expo-router";
import { Routes } from "expo-router/types";
export default function Home() {
const router = useRouter();
return (
<div>
<Link href="/settings">Go to Settings</Link>
<button onPress={() => router.push({ pathname: "/user/42", params: { id: "42" } })}>
Go to User 42
</button>
</div>
);
}
Hovering over href or router.push in VS Code will show autocompletion for all defined routes.
Dynamic Segments and Params
Dynamic routes use brackets. For a route app/user/[id].tsx, the id param is extracted via:
import { useLocalSearchParams } from "expo-router";
export default function User() {
const { id } = useLocalSearchParams();
return <div>User ID: {id}</div>;
}
Catch‑all routes are declared with three dots: app/docs/[...slug].tsx, giving slug as an array.
Deep Linking and Universal Links
Set the custom URL scheme in app.json:
{
"expo": {
"scheme": "myapp"
}
}
Testing on iOS: npx uri-scheme open "myapp://settings" --ios. On Android: npx uri-scheme open "myapp://settings" --android.
For universal links, add associatedDomains (iOS) and assetlinks.json (Android) via EAS Build. The router automatically handles the link and navigates to the matching route.
Web Export and Static Rendering
Export a static web build:
npx expo export --platform web
The generated dist folder contains one HTML file per route. The web output uses hash‑based routing by default; to get clean URLs you must host the static files behind a server that rewrites all paths to index.html or use web.output: "static" with a custom server.
SSR is not supported in the managed workflow; if you need server‑side rendering, consider a Next.js or custom server setup.
Layouts, Nested Navigators, and Modals
Place a _layout.tsx file to wrap all child routes. It can define a stack, tab, or drawer navigator:
import { Tabs } from "expo-router";
export default function Layout() {
return (
<Tabs screenOptions={{ headerShown: false }}>
<Tabs.Screen name="index" options={{ title: "Home" }} />
<Tabs.Screen name="settings" options={{ title: "Settings" }} />
</Tabs>
);
}
Nested layouts (e.g., app/user/_layout.tsx) can override navigation options. Avoid double headers by setting headerShown: false on child layouts or using unstable_settings to control the initial route.
When a screen is presented as a modal (presentation: "modal"), router.back() does not dismiss it. Use router.dismiss() or a dedicated dismiss action defined in the modal’s layout.
Common Mistakes & Limitations
- Placing non‑route components inside
app– they become routes. Keep reusable code incomponents/orapp/_components/. - Assuming
router.back()works after a modal. Userouter.dismiss()instead. - Using plain JavaScript projects – typed routes require TypeScript 5.3+ and the Expo Router Babel plugin.
- Attempting programmatic route registration – all routes must exist as files at build time.
- Relying on SSR in managed workflow – not supported. Use a custom server or Next.js if needed.
- Expecting hash‑less URLs without a server – web export defaults to hash routing. For clean URLs, host with a rewrite rule or use a custom server.
- Overlooking native module integration – modules that intercept navigation must use the Expo Router native API or drop to bare workflow.
- Misconfiguring deep links – ensure
schemeand associated domains are correctly set and that EAS Build is used for iOS universal links.
By following the steps above and being aware of these pitfalls, you can confidently adopt Expo Router for a scalable, type‑safe navigation experience in SDK 50+.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.