Benji Riethmeier

Courtside Devlog 9: Navigation With Expo Router

3/27/2023 | 8 min read

What is Courtside (Currently)

Not much work has been done in the past few weeks due to spring break and presentations in class. The app is much in the same state as the last devlog. The NHL integration is still in progress on the backend and the frontend has received many nice-to-have features such as theming configuration and being able to pull more types of data to the frontend. We also have a more testing coverage for frontend components.

This Week

This week is the last one of the sprint and comes after a long break in development due to spring break and presentations. Since the NHL integration is not ready for the frontend, I decided to experiment with the new Expo Router and see how it could be implemented in our app.

The Current State of Navigation

Currently, for navigation we use React Navigation , which gives us a lot of options in how we present navigation in the app. There are three main types of Navigators:

  1. Tab Navigators
  2. Stack Navigators
  3. Drawer Navigators

Tab Navigator

The Tab Navigator is the easiest one to see in the courtside app, as it powers navigation on our main navigator. Below, you can see the four tabs on our main navigator.

The four tabs in the main Courtside navigator

The four tabs in the main Courtside navigator

Here is the code to implement a tab navigator through React Navigation.

const Tab = createBottomTabNavigator();

const MainNavigation = () => {
    <Tab.Navigator initialRouteName="Stats" screenOptions={options}>
        <Tab.Screen name="Stats" component={StatsStack} />
        <Tab.Screen name="Games" component={ScheduleStack} />
        <Tab.Screen name="Rosters" component={RosterStack} />
        <Tab.Screen name="Settings" component={Settings} />
//note: on top of this, there is an additional 50 lines to create the options object

Stack Navigator

The Stack navigator is built off of a navigation stack. As the user goes to a new screen, that screen is added to the top of the navigation stack. If the user clicks the back button, then the current screen is removed from the stack and the user goes to the screen below it. These are harder to see within the Courtside app, but they make up the bulk of our navigation.

Each tab in our main Tab Navigator is a Stack Navigator, which contains all the screens a user can navigate to in that tab. For instance, on the Teams page of our app you can navigate to three types of screens: a screen to add new teams, a screen to view data about an individual team, and a screen to view data about a player.

The video below shows this navigation in action.

As the user navigates through the pages this how the stack looks over time.

  1. Team Dashboard
  2. Team Dashboard > Add Team
  3. Team Dashboard
  4. Team Dashboard > Atlanta Hawks
  5. Team Dashboard > Atlanta Hawks > Dejounte Murray
  6. Team Dashboard > Atlanta Hawks
  7. Team Dashboard

Below you can see the code required to create the teams stack as well as the types that are needed.

export type RosterNavigatorParamList = {
    Dashboard: undefined;
    Team: { team: Team };
    Selection: undefined;
    Player: { player: Player; team: Team };

export type TeamNavigationProp =
const Stack = createNativeStackNavigator<TeamNavigatorParamList>();

export const TeamsStack = () => {
    return (
            screenOptions={{ headerShown: false }}
            <Stack.Screen name="Dashboard" component={RostersScreen} />
            <Stack.Screen name="Selection" component={TeamSelectionScreen} />
            <Stack.Screen name="Team" component={TeamScreen} />
            <Stack.Screen name="Player" component={PlayerScreen} />

Drawer Navigator

A drawer navigator features a sidebar that the user can open, which shows the different places the user can navigate to.

A diagram of a drawer navigator

A diagram of a drawer navigator

Currently, we do not use this type of navigator in our app.

Downsides to React Navigation

This current approach requires a lot of code and a separate directory for all the navigation features of our app. Each navigator alos requires a lot of boilerplate to get set up.

This makes it difficult for our frontend engineers to contribute meaningfully to the navigation aspects of the frontend because it involves learning a whole new package and familiarizing oneself with an entire new part of the project.

  • pages/
    • AccountScreen.tsx
    • FavoritePlayersScreen.tsx
    • ForgotPasswordScreen.tsx
    • GetStartedScreen.tsx
    • ...
  • navigation/
    • AuthStack.tsx
    • MainNavigation.tsx
    • TeamsStack.tsx
    • ScheduleStack.tsx
    • ...
  • types/
    • Navigation.d.ts

The current structure of our app (it's not great)

Playing with Expo Router

The new Expo SDK 48 brings in a new system for navigation. It implements a file-based routing system similar to the ones seen in frontend metaframeworks like Next.js and SvelteKit .

With this new router, routes can now be modelled like a file system. The first step is to create a top level folder called app/ in the project * It is convention that all routes be in a folder called /app. . The subdirectories of this app/ directory are the navigators from the previous implementation, and the files are the screens.

For instance, the Team Stack shown above could be implemented like this. The files represent screens and directories represent navigators. With this new setup, the organization of the app is immediately apparant from a setup like this.

  • teams/
    • _layout.tsx
    • dashboard.tsx
    • addTeams.tsx
    • [team]/
      • index.tsx
      • [player].tsx

The Teams Navigator with the new Expo Router

The teams folder turns into a stack navigator and the addTeams, dashboard, [team] * If a file is named index then the route will take the directory’s name for instance team/[team]/index.tsx represents the page at the URL team/[team]. (Read on to see what the bracket notation means!) , and [player] files are the separate screens and if this were a website these screens would be mapped to the following URLs: /teams/addTeams, /teams/dashboard, /teams/[team], and /teams/[team]/[player].

The [] convention means this is a dynamic route, i.e., it will render different data depending on the value of team that is passed in. For instance, if you navigate to /teams/ATL it would render data about the Atlanta Hawks. With this notation, we create one file that can be used for all the teams that we have to render.

The _layout.ts file seen above is a file convention which specifies the type of router that you want to use in the current directory. For the teams stack navigator, it would look like this.

import { Stack } from "expo-router/stack";

export default function Layout() {
    return <Stack initialRouteName="dashboard" />;

The Link component can then be used within pages to navigate to other pages. The example below would allow a user to navigate from the dashboard screen to the addTeams screen.

import { Link } from "expo-router";

export default function Dashboard() {
    return (
            <Link href="/teams/addTeams">Add More Teams</Link>

The tab navigator we use for the main navigation could be implemented like this, where each subdirectory is a stack navigator, just like the Teams Stack shown above.

  • app/
    • (main)/
      • _layout.ts
      • stats/
        • _layout.ts
        • ...
      • games/
        • _layout.ts
        • ...
      • teams/
        • _layout.ts
        • ...
      • settings.tsx

The Main Navigation using the new Expo Router

After that we just need to include the auth navigator (which is currently implemented as its own separate stack) and then our whole app will be migrated over.

This implementation would remove our need for the current pages and navigation folders and would help make the structure of our app more apparent to our entire team.

Status of the Transition and Problems

Currently, my work on transitioning to the Expo Router has been contained to a separate branch for now. I’ve been playing around with it, and I’m still trying to make sure that the behavior with Expo Router will match what the team has come to expect from our app. I’ve transitioned most of the main navigator similarly to what I demonstrated above, but I have not begun work on the navigator for auth.

To bring this into the main app, we will have to update the rest of the project from SDK 47 to SDK 48. This should not be too difficult as there are code mods and command line utilities that help update to the latest version, but there still might be unexpected difficulties with this.

Another difficulty with this is the large changes that have to be done to the structure of our project and ensuring that nothing breaks along the way.

I foresee that I will be keeping this project separate for another week or two (probably two) to ensure that everything is working properly.

Photo by Klim Musalimov from

Photo by Klim Musalimov from

Other general difficulties include the integration of NHL data into the app being more difficult and extensive than we previously thought. It turns out integrating it on the frontend might require some large overhauls to the current navigation structure and data fetching paradigm. However, this will hopefully be made easier with the transition to using the new Expo Router.

Next Week

Overarching Goals

This week is the presentation for Sprint 6, so the team will be mostly focused on preparing for that. For me, this might mean creating a few more unit tests on the frontend to pass the threshold of the amount of tests required for this sprint.

Unfortuantley, the NHL integration will not be completed this sprint and will be continued into the next sprint. If anything, this is the main goal our team wants to achieve before the school year (and our project) ends.

Pet Project

My pet project this week is going to be continuing the work with the Expo Router and hopefully getting that integrated into the main app as soon as possible.

And that’s it! I’ll be back next week with another report!