In the dynamic and constantly evolving world of mobile application development, choosing the right navigation tools can significantly impact the effectiveness and efficiency of your projects. Among these tools, expo-router has emerged as a strong contender. It aims to redefine our understanding of navigation in React Native applications by offering a unique approach to handling navigation. This approach includes several promising features that streamline the development process, bolster user experience and future-proof apps.
However, like all technologies, expo-router has certain drawbacks. This article delves into the advantages and drawbacks of expo-router, providing insights into its functionality and potential use cases. Additionally, we will share our plans concerning its adoption in our future projects.
In React Navigation, you define your navigation structure using JavaScript objects and React components. Here's a simple example:
The declarative nature of React Navigation means that you don't have to manually manage the state of your navigation or handle transitions between screens. Instead, you define the structure of your navigation (with navigators and screens declaration) and the conditions under which certain screens should be displayed, and React Navigation handles the rest. This makes it easier to create dynamic navigators based on application state.
However, declaring each route and each navigator involves a lot of manual configuration and can lead to maintainability issues as your application grows. Here comes expo-router and its file-based routing paradigm. ⬇️
With file-based routing, the framework automatically generates routes based on your project's file structure, removing the need for explicit route configuration. Your page's directory becomes an immediate reflection of your application's routes.
Becomes:
If you want to learn how to use the API, check out the following article. It provides a hands-on example of building an app with two tabs using ++code>expo-router++/code>.
The documentation provides a detailed explanation of how to set up and use expo-router. If you are unfamiliar with the library, it is recommended that you read the documentation first as always.
During discussions about expo-router with some React Native developers, several questions arose and I tried to summarize them in this section.
Since It’s V1, expo-router:
At AppJS, Evan Bacon announced some new features for version 2 of expo-router, making it the best solution for developing a universal app. These include:
Type-safe navigation is a key feature to protect our apps from runtime crashes. react-navigation has come with numerous ways to tackle it over the years:
At the moment, expo-router V1 does not support Typescript. As a result, navigating and handling parameter errors can be difficult. However, Typescript support will be available with expo-router V2 and the release of Expo SDK 49. You can get a glimpse of how it works by checking out this example repository.
Beware that to make it work, you should set the environment variable ++code>EXPO_USE_TYPED_ROUTES=true++/code> as explained in the documentation.
A significant advantage of expo-router is that all navigation types are generated by the CLI and synchronized with the file system, eliminating the need for manual handling. The library also uses them automatically.
While type-safe navigation is important, it is not always enough. In a previous article, I demonstrated a scenario using react-navigation that can lead to errors even when there are no TypeScript errors, due to a poor handling of the typing of ++code>useNavigation++/code>.
In this situation, if the code ++code>navigation.navigate("ScreenTwo")++/code> is called from ++code>ScreenThree++/code>, we’ll have a runtime error because ++code>ScreenTwo++/code> is not defined in the same navigator as ++code>ScreenThree++/code>. Instead of pushing arbitrary screen identifiers to the stack, a possible solution is to navigate from the root of the app, with the following invocation :
++code>navigation.navigate("ChildNavigatorOne", { screen: "ScreenTwo" });++/code>
URL-based navigation uses the exact same pattern to declare the navigation from the root of the application. expo-router always know exactly where to find the screen associated with a static URL, protecting you from the above issue.
For dynamic URLs modified at runtime, you still have a risk to build an invalid URL. Hopefully, you can implement a dynamic route that will match all unhandled navigation in expo-router.
expo-router is built above react-navigation so you’ll have access to the same features. If you want to implement mobile-specific navigation like a drawer, you can reuse the navigator from @react-navigation/drawer as explained in the documentation. All properties available in react-navigation will be accessible, you don’t have to make any tradeoffs on customization. Other navigations are available :
For basic navigation use cases, expo-router V2 will be simpler because you won’t have to define your navigators and their types, expo-router will handle that for you. You’ll be able to focus more on your application code.
However, for advanced use cases, you’ll have to come back to react-navigation APIs. For many production apps, knowledge about both libraries and paradigms will be required.
Deep linking configuration with react-navigation has always been difficult to achieve. You need to manually configure a mapping between the paths and the screen they target and pass it to the linking property of the ++code>NavigationContainer++/code>. The maintenance of the linking config is error-prone.
Good news! expo-router provides automatic deep linking configuration and it works like a charm. However, react-navigation will offer the same feature in version 7.
By default, expo-router uses the ++code>app++/code> folder at the root of the Expo project to generate the navigation. At BAM, we have an old habit to declare all our app code inside an ++code>src++/code> folder and I would love to stick with it.
It’s also a concern that has been raised by Jamon Holmgren, as Ignite adopted a similar convention. Unfortunately, at the moment expo-router makes assumptions about the default path, so forcing it to look into another folder may lead to bugs. Fortunately, when expo-router becomes stable, it will be merged into the expo/expo repo and the path customization may become available.
In a previous article, I was advocating about the benefits of isolating the navigation from the rest of your app. One particular point has become more relevant than ever since the arrival of expo-router:
It’s easier to change your navigation library. Let’s be honest, if you use React Navigation for your app, you’ll probably stay with it for the app's lifetime. However, It’s more and more frequent today to generate a web app from a React Native codebase thanks to React Native Web. Unfortunately, React Navigation support for the web is still experimental. You may prefer to rely on a framework like NextJS and its routing features instead.
expo-router is the library that may make you ditch (at least partially because it still heavily relies on it) react-navigation. The migration from react-navigation to expo-router will be easier if your screens and your navigators are isolated from the rest of your codebase.
In the expo-router setup documentation, you’ll be asked to configure your entry point ++code>index.js++/code> to contain only the following import:
++code>import "expo-router/entry";++/code>
This will export the root component of expo-router that will load all our app screens from our ++code>app++/code> folder. You may have noticed that we loose our famous ++code>App.tsx++/code> component where our app is configured.
The solution of our problem relies on the layout feature available in expo-router. You can create a root layout that will set your App contexts and return the ++code>Slot++/code> component from expo-router, responsible for rendering the rest of the navigation.
expo-router is an exciting development in the React Native ecosystem, promising to redefine how we approach navigation in mobile applications. By automating the generation of routes based on file structure, expo-router enforces architectural best practices, paving the way for more intuitive and maintainable codebases. It also anticipates the future of web support, making it a forward-thinking choice for developers keen on universal application development.
However, like all technologies, expo-router is not without its drawbacks. As of now, we're still awaiting the stable release of V2, which promises enhanced TypeScript support and other critical features. The rigidity of its file-based system could be restrictive in some contexts. Moreover, for advanced use cases, you may find yourself needing to master both expo-router and react-navigation, which can lead to a steeper learning curve.
Despite these concerns, the advantages of expo-router are quite compelling, particularly when considering new projects. While its current version may lack certain features, the upcoming V2 seems promising and could be a game-changer when it's released and stabilized. In the worst-case scenario, its architecture and APIs should allow for a swift fallback to react-navigation if necessary. As for existing projects, there's no urgent need for migration, except potentially to the static API of react-navigation if it simplifies deep link configuration.