Expo Router Guide for React Native: File-Based Navigation, Authentication, and Deep Linking
React NativeExpoExpo RouterNavigationAuthenticationDeep LinkingMobile Development

Expo Router Guide for React Native: File-Based Navigation, Authentication, and Deep Linking

RReact Native Editorial Team
2026-08-03
9 min read

A practical Expo Router checklist for React Native tabs, stacks, authentication, protected routes, deep links, modals, and testing.

This Expo Router guide gives you a reusable checklist for structuring React Native navigation, combining tabs and stacks, protecting screens during authentication, handling deep links, adding modal routes, and testing navigation before release. The examples use conventional file-based routing patterns; verify command names and configuration details against the Expo and Expo Router versions used by your project.

Overview

Expo Router builds on React Navigation while mapping files and folders to routes. Instead of keeping every screen in a large navigation configuration, you organize screens inside an app directory. A file such as app/settings.tsx becomes a settings route, while a folder such as app/(tabs) can group tab screens without adding the group name to the URL.

A small project might begin with this structure:

app/
  _layout.tsx
  index.tsx
  sign-in.tsx
  (tabs)/
    _layout.tsx
    home.tsx
    profile.tsx
  details/
    [id].tsx
  modal.tsx

The root _layout.tsx is the place to define navigation that applies across the application. A nested layout defines navigation for one branch, such as a tab bar. Parentheses create a route group: they help organize navigation without necessarily becoming part of the visible path. Square brackets create dynamic segments, so details/[id].tsx can represent routes such as /details/42.

File-based navigation is most useful when the route tree is easy to understand. It is not a substitute for navigation design. Before creating files, decide which screens are public, which screens require a session, which screens belong in tabs, and which transitions should behave like a stack, modal, or replacement. This prevents the folder structure from becoming an accidental product specification.

For TypeScript projects, keep route parameters and screen data typed. The TypeScript in React Native guide provides a useful foundation for strict configuration and safer component boundaries.

Checklist by scenario

Starting a new Expo Router project

  1. Confirm that the project template includes Expo Router and that the router entry point is configured as expected.
  2. Keep the route directory focused on screens and layouts. Move reusable UI, API clients, hooks, and domain logic into separate directories.
  3. Create a root layout before adding many screens. Decide whether it owns a stack, a session gate, global providers, or some combination.
  4. Choose route names that describe user-facing destinations rather than implementation details. Consistent names make links, analytics, and test cases easier to read.
  5. Run the application on each target platform early. A route that looks correct in a web-like development environment still needs mobile back-button, gesture, and deep-link checks.

Combining tabs and stacks

A common React Native navigation pattern is a tab navigator at the main level with a stack inside each tab. In Expo Router, the parent and child layouts express that relationship:

// app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router';

export default function TabsLayout() {
  return (
    <Tabs>
      <Tabs.Screen name="home" options={{ title: 'Home' }} />
      <Tabs.Screen name="profile" options={{ title: 'Profile' }} />
    </Tabs>
  );
}

If a tab needs its own detail screen, place a nested stack below that tab rather than pushing every detail route into the root. This preserves the expected back behavior: a user can open a detail screen from a tab, go back to the tab’s list, and switch tabs without losing the overall navigation model.

  • Use tabs for stable top-level destinations that users switch between.
  • Use stacks for forward navigation within a task or content hierarchy.
  • Keep transient tasks, such as editing or confirmation, separate from the tab bar when a modal presentation is clearer.
  • Set titles and accessibility labels explicitly when the file name is not suitable for display.

Adding authentication and protected routes

Authentication is a state transition, not merely a sign-in screen. Your navigation must account for at least three states: the session is loading, the user is authenticated, or the user is signed out. Rendering protected content while the session is still being restored can cause flashes of the wrong screen and confusing redirects.

Keep session state in a provider or dedicated hook, and persist credentials using a storage mechanism appropriate to their sensitivity. The React Native local storage comparison can help separate ordinary cached data from values that require secure storage.

// app/_layout.tsx
import { Stack, Redirect, Slot } from 'expo-router';
import { useSession } from '../src/auth/useSession';

export default function RootLayout() {
  const { status } = useSession();

  if (status === 'loading') {
    return <LoadingScreen />;
  }

  if (status === 'signedOut') {
    return <Redirect href="/sign-in" />;
  }

  return <Stack>
    <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
    <Stack.Screen name="modal" options={{ presentation: 'modal' }} />
  </Stack>;
}

Treat the example as a routing shape rather than a complete authentication system. The session hook still needs to restore and clear credentials, handle expired tokens, and avoid redirect loops. Public routes also need a deliberate policy: either allow signed-in users to visit them or redirect them to an appropriate authenticated destination.

Deep linking lets an external URL open a specific route. Typical examples include an email verification link, a notification that opens a conversation, or a shared content URL. Start by defining the route that should receive the link, then decide what happens when the app is cold-started, already open, signed out, or missing the referenced resource.

  1. Give the app a stable scheme or associated web URL configuration appropriate to your release setup.
  2. Use route paths that are readable and stable. Avoid exposing temporary UI state in a link that users may save or share.
  3. Validate dynamic parameters before fetching data or rendering sensitive content.
  4. Handle authentication links separately from ordinary content links. A sign-in or verification link may need to restore a pending destination after the session changes.
  5. Test links from a cold start, a backgrounded app, and an already active app on both major mobile platforms.

For platform configuration details and a broader comparison with React Navigation linking, see How to Handle Deep Linking in React Native with Expo Router and React Navigation.

Adding modal screens

A modal route is still a route, so it should have a predictable path and back behavior. Add it to the relevant stack and choose a presentation style in the layout. Use modals for focused tasks that users can dismiss without losing their place, such as filters, a short form, or a confirmation step. Avoid putting a long multi-screen workflow in a modal unless its dismissal and recovery behavior are well defined.

Check the hardware back button, swipe dismissal, screen-reader focus, keyboard behavior, and what happens if the modal is opened directly from a deep link. Navigation presentation is part of accessibility; review the React Native accessibility checklist before treating the route as complete.

Testing navigation flows

Test routes as user journeys rather than isolated snapshots. At minimum, cover signed-out entry, successful sign-in, sign-out, session restoration, tab switching, stack back behavior, a dynamic route with an invalid identifier, modal dismissal, and a deep link received in each app state.

Separate routing tests from network and authentication-provider tests where possible. Mock the session boundary and assert which route is rendered for each session state. Then use an end-to-end test on a real or representative build to verify native linking, gestures, and platform back behavior. The React Native testing strategy explains how unit, integration, and end-to-end coverage can work together.

What to double-check

  • Route ownership: Every screen should have one clear parent layout. Unclear ownership often produces duplicate headers, unexpected tab bars, or inconsistent back behavior.
  • Loading boundaries: Do not redirect until session restoration has completed. Show a deliberate loading state instead of briefly rendering a protected screen.
  • Parameter validation: Dynamic route values come from navigation input. Treat them as untrusted strings and validate them before using them in queries or privileged actions.
  • State restoration: Decide whether returning to a screen should preserve its form, scroll position, filters, and selected tab. Do not assume navigation will restore every piece of application state.
  • Back behavior: Document what back means from a modal, a nested detail screen, the first tab screen, and a deep-linked destination with no previous route.
  • Credential handling: Keep tokens and refresh logic out of presentation components. Navigation should consume session state, not implement the entire auth protocol.
  • Accessibility: Check focus order, labels, dynamic text size, contrast, and whether route changes are understandable to assistive technology. Navigation success is not only visual.
  • Platform differences: Verify headers, gestures, safe areas, keyboard avoidance, and system back actions on both iOS and Android. A shared route tree does not guarantee identical platform behavior.
  • Link resilience: Decide how to handle an unknown route, an expired invitation, a deleted record, or a link opened before required app data is available.

Common mistakes

Putting every screen in one root stack

A single stack may be quick to start with, but it becomes difficult to reason about as tabs, settings, onboarding, and detail screens accumulate. Group routes by user journey and use nested layouts when a branch has its own navigation rules.

Using redirects as the only authentication design

A redirect can select a destination, but it does not solve token refresh, loading states, sign-out cleanup, or pending deep links. Keep those responsibilities in the session layer and make the router respond to a small, explicit state model.

Changing route names casually

Route names can appear in push payloads, emails, analytics, bookmarks, and tests. When a route must change, consider a compatibility path or migration behavior rather than silently breaking old links.

Mixing server data with route parameters

A route such as /details/[id] identifies a destination; it does not guarantee that the record exists or that the user can view it. Fetch, authorize, and handle missing data inside the screen’s data boundary.

Testing only from a clean launch

Navigation bugs often appear after a session expires, a link opens over an existing screen, a user presses back twice, or a modal is dismissed with a gesture. Include these transitions in manual checks and automated coverage.

When to revisit

Revisit this checklist before a major navigation change, an authentication redesign, a new tab or modal workflow, or a release that introduces deep links. It is also worth reviewing before seasonal planning cycles when teams often add campaigns, notification destinations, and temporary user journeys.

Repeat the platform checks whenever Expo, Expo Router, React Navigation, the operating systems, or your build configuration changes. Tooling updates can affect route entry points, native linking, gestures, or development commands even when the screen code remains unchanged. Keep a small navigation smoke test in CI so a dependency update cannot silently remove a critical route.

For a practical maintenance pass, walk through this sequence:

  1. Print or inspect the route tree and mark each route as public, protected, modal, dynamic, or grouped.
  2. Test session loading, signed-out, signed-in, expired-session, and sign-out states.
  3. Open every important deep link from a cold start and an active app, including an invalid or unauthorized target.
  4. Verify tab history, stack back behavior, modal dismissal, keyboard handling, and accessibility focus.
  5. Run navigation tests on both mobile platforms and record any intentional platform-specific behavior.
  6. Update route documentation and test fixtures whenever a path, parameter, or authentication rule changes.

Used this way, Expo Router remains more than a convenient file convention: it becomes a visible contract between your app’s screens, authentication state, external links, and release tests.

Related Topics

#React Native#Expo#Expo Router#Navigation#Authentication#Deep Linking#Mobile Development
R

React Native Editorial Team

Technical Editor

Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.