skip to content

File-System Router

Expo Router turns the files under app/ into navigators, URLs and deep links, running React Navigation's navigators underneath. Interviewers ask what that buys over hand-configured navigators.

part ofExpo (React Native)overview, primer and where to startread it →
on this pageshow

explore

questions

25

With Expo Router, how does the src/app directory turn files into routes, and what does a _layout.tsx file add to that tree?

level: juniorimportance: must knowfreq 62%

answer

  1. the file tree is the navigator tree
  2. default export means one screen
  3. path under src/app becomes the URL
  4. _layout renders before its children
  5. no root layout: a generated Slot

basics

~20 s

Each file under src/app with a default export becomes a screen whose URL mirrors its path, and each _layout.tsx wraps its directory's routes in a navigator such as Stack, Tabs or Slot, so the folder tree becomes the navigator tree.

solid answer

~40 s

In Expo Router every file under `src/app` (or a root `app/` in older projects) is a route: its default-exported component is the screen, and its path is its URL, so `src/app/cookbook/desserts.tsx` answers `/cookbook/desserts` on web and as a deep link on Android and iOS. A `_layout.tsx` file is not a screen. It renders before the routes in its directory and decides how they relate: return `<Stack />` for a native stack, `<Tabs />` for bottom tabs, or `<Slot />` to render the current child with no navigator. Layouts nest, so the directory tree is the navigator tree. The root `src/app/_layout.tsx` replaces `App.tsx` for fonts, providers and the splash screen, and shared components live outside `src/app` because anything inside it is treated as a route.

code

tsx · 11 lines
tsx
// src/app/_layout.tsx
import { Stack } from 'expo-router';

export default function RootLayout() {
  return (
    <Stack>
      <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
      <Stack.Screen name="settings" options={{ title: 'Settings' }} />
    </Stack>
  );
}

go deeper

for a junior

Recall the two rules: a file under src/app with a default export is a screen whose path is its URL, and a _layout.tsx wraps its folder's routes in a navigator such as Stack or Tabs.

for a middle

Explain that layouts nest, so the folder tree is the navigator tree, that routes register automatically, and that Stack.Screen exists only for options and ordering.

for a senior

Show you treat the file tree as public API: renaming or moving a route changes URLs that deep links and notifications depend on, and moving it across a layout changes its navigator.

for a principal

Weigh file-based routing against a hand-written navigator config for a large app: URL-first addressability and web parity versus coupling the directory structure to the product's link contract.

## The one rule: the file tree is the route tree **Expo Router** is the file-based router that ships with Expo. Instead of declaring screens in a navigator configuration, you create files. In a project made from the default template (SDK 55 and later) the routes live in **`src/app`**; an older project may still use a root **`app/`** directory, and when both exist Expo Router uses `src/app` only. The rule has three consequences: - **Every file with a default export is a screen.** `src/app/settings.tsx` renders whatever React component it exports as default. - **Every screen has a URL that mirrors its path.** `src/app/cookbook/desserts.tsx` answers `/cookbook/desserts` in a browser, and the same path works as a deep link on Android and iOS once the app has a URL scheme. - **Only the `src/app` prefix and the file extension are dropped.** Folder and file names become URL segments as written, except for the special notations (parentheses, brackets, `_layout`, `+` files). ## `_layout.tsx`: where navigators come from A file named **`_layout.tsx`** is not a page and has no URL of its own. It defines how the routes in its directory are arranged, and it renders **before** them. What it returns decides the navigator: - `<Stack />` from `expo-router` turns the directory's routes into screens of a **native stack**: pushing adds a screen with a header and a back button, and screens underneath stay mounted. - `<Tabs />` (imported from `expo-router/js-tabs` in SDK 57) turns them into tabs of a **JavaScript bottom-tab navigator**. - `<Slot />` renders the current child route in place with **no navigator at all**, which suits wrapping routes in a shared header, footer or provider. Because layouts nest, the **directory tree becomes the navigator tree**. A layout in `src/app` that returns a `Stack` and another in `src/app/(tabs)` that returns `Tabs` give you a stack whose first screen is a tab bar. You do not register screens one by one: every route file in the directory is added automatically, and `Stack.Screen` or `Tabs.Screen` children exist only to set options or order. ## The root layout replaces App.tsx `src/app/_layout.tsx` is the **entry point** of the app. Initialisation that used to live in `App.tsx` goes here: loading fonts, holding and then hiding the splash screen, and mounting context providers such as a theme. If a project has **no root layout**, Expo Router generates one that renders the current route through a `Slot`, so there is no header and no stack history on screen until you write your own. ## What does not belong in src/app Anything under `src/app` is treated as a route, which produces three common mistakes: 1. Putting a shared `RecipeCard.tsx` in `src/app/components/` gives it the URL `/components/RecipeCard`; keep components in `src/components`, hooks in `src/hooks`, constants in `src/constants`. 2. A route file with no default export triggers a development warning that the route is missing its required default export. 3. Config files (`app.json`, `app.config.ts`, `metro.config.js`, `tsconfig.json`, `package.json`) stay at the project root, never inside `src`. ## The recipe app, mapped | File | Role | URL | |---|---|---| | `src/app/_layout.tsx` | root layout returning a `Stack` | none | | `src/app/(tabs)/_layout.tsx` | tab navigator | none | | `src/app/(tabs)/index.tsx` | Recipes tab | `/` | | `src/app/(tabs)/saved.tsx` | Saved tab | `/saved` | | `src/app/cookbook/desserts.tsx` | stack screen | `/cookbook/desserts` | | `src/app/settings.tsx` | stack screen | `/settings` | | `src/app/+not-found.tsx` | fallback for unmatched URLs | any unmatched path | The parentheses in `(tabs)` mark a **route group**, a folder that shapes the navigator tree but adds nothing to the URL; that is why the Saved tab lives at `/saved`, not `/tabs/saved`. ## Why interviewers ask it The question checks whether a candidate sees Expo Router as **URL-first**: every screen is addressable, so deep links and web support fall out of the file layout instead of a separate linking config. The trade-off is that the file system is now public API. Renaming `desserts.tsx` renames a URL that old links, push payloads and bookmarks may still point at, and moving a file across a `_layout.tsx` boundary moves it into a different navigator. A strong answer names both halves: files make routes, layouts make navigators, and the two trees are the same tree.

  • What does Expo Router render if a project has no src/app/_layout.tsx?
    It generates a default root layout that renders the current route through a `Slot`. There is no navigator, so no header, no back button and no stack history until you add a `_layout.tsx` that returns a `Stack`, `Tabs` or another navigator.
  • Why must a shared RecipeCard component live outside src/app?
    Expo Router treats every file under `src/app` as a route, so `src/app/components/RecipeCard.tsx` would get the URL `/components/RecipeCard` and could be navigated to. Components, hooks and constants belong in sibling folders such as `src/components` and are imported by route files.
  • Can an Expo Router project keep its routes in a root app/ directory instead of src/app?
    Yes. Older projects use `app/`, and it still works. When both `app/` and `src/app/` exist, only `src/app` is used. A custom root can be set through the `root` option of the `expo-router` config plugin, but Expo discourages it because tooling assumes one of the two defaults.

A building directory where each door (file) has a room number (URL) and each floor's layout plan (_layout.tsx) decides whether rooms open off a corridor you walk back along (Stack) or off a lobby you switch between (Tabs).

saying these in an interview costs you the question

  • Every screen still has to be registered by hand in a navigator config.
  • A _layout.tsx file is itself a screen with its own URL.
  • Shared components can sit inside src/app without any side effects.
  • A folder without its own _layout.tsx is ignored by the router.
  • Fonts and providers still load in App.tsx next to Expo Router.
open as a page

In Expo Router, how does a src/app/product/[id].tsx file capture a URL segment, and how does the screen read it?

level: juniorimportance: must knowfreq 60%

basics

~10 s

Square brackets make a dynamic segment: src/app/product/[id].tsx matches /product/42 and any other single segment, and the screen reads it with useLocalSearchParams(), which returns id as the string '42'.

open as a page

In Expo Router, how do (group) folders and index.tsx files shape URLs, and how do you make a tab group the app's first screen?

level: middleimportance: must knowfreq 55%

basics

~20 s

A folder named in parentheses, such as (tabs), groups routes under a shared layout without adding a URL segment, and index.tsx is its folder's default route, so src/app/(tabs)/index.tsx answers / and the tab bar becomes the first screen.

open as a page

In Expo Router, how do useLocalSearchParams and useGlobalSearchParams differ when several /product/[id] screens are stacked, and which should a screen use?

level: middleimportance: must knowfreq 50%

basics

~20 s

useLocalSearchParams returns the params of the route the component belongs to and ignores other screens' URLs; useGlobalSearchParams follows the currently focused URL and re-renders background screens on every change, so screens should use the local hook.

open as a page

In Expo Router 57, how do router.navigate, router.push and router.replace differ in what they add to a stack's history?

level: middleimportance: must knowfreq 55%

basics

~20 s

push always adds a stack entry; navigate adds one unless the target is the screen already on top with the same path params, which it updates in place; replace swaps the current entry, keeping history length unchanged.

open as a page

In Expo Router, what renders when a URL or deep link matches no file under src/app, and how does +not-found.tsx customise it?

level: juniorimportance: should knowfreq 30%

basics

~10 s

Expo Router always adds a not-found route that renders its built-in Unmatched screen; a src/app/+not-found.tsx default export replaces it, and a +not-found.tsx inside a folder handles unmatched URLs under that folder.

open as a page

With Expo Router, how do you present a streaming app's plan-picker route as a modal or a draggable sheet instead of a pushed screen?

level: juniorimportance: should knowfreq 42%

basics

~10 s

Declare the route in its stack layout with a Stack.Screen whose options set presentation: 'modal', or presentation: 'formSheet' plus sheetAllowedDetents such as [0.5, 1] for a sheet that rests at half and full height.

open as a page

In Expo Router, why does <Stack.Screen name="desserts"> in the root layout warn 'No route named' when the file is src/app/cookbook/desserts.tsx?

level: middleimportance: should knowfreq 35%

basics

~20 s

With no cookbook/_layout.tsx, the file is hoisted into the root layout under the name cookbook/desserts, so name="desserts" matches nothing; use that relative name, or add a cookbook layout only if the folder needs its own navigator.

open as a page

In Expo Router, what do usePathname and useSegments return on /product/42, and when would you reach for each?

level: middleimportance: should knowfreq 32%

basics

~20 s

usePathname returns the normalized URL path without the query, '/product/42', while useSegments returns the matched file segments as written, ['product', '[id]'], including any (group) names, so pathname suits display and analytics and segments suit structural checks.

open as a page

In Expo Router, why are params always strings or string arrays, and what does a src/app/docs/[...slug].tsx route receive for /docs/guides/install?

level: middleimportance: should knowfreq 38%

basics

~10 s

Expo Router resolves every navigation to a URL and parses params back out of it, so values are strings; a [...slug] catch-all collects all remaining segments into a string array, giving slug ['guides', 'install'].

open as a page

With Expo Router, how does Stack.Protected's guard prop keep a streaming app's account screens from signed-out users, and where does a blocked user land?

level: middleimportance: should knowfreq 38%

basics

~20 s

Screens wrapped in <Stack.Protected guard={isSignedIn}> are left out of the navigator while the guard is false. A deep link to one lands on the anchor route or first available screen, and a guard flipping false removes their history entries.

open as a page

In Expo Router, how do router.back, dismiss, dismissTo and dismissAll differ when closing a multi-step refill flow inside a stack?

level: middleimportance: should knowfreq 35%

basics

~20 s

router.back goes back however the focused navigator defines it; dismiss(count) pops that many stack screens; dismissTo(href) pops until href is on top or replaces the current screen if it is absent; dismissAll returns to the nearest stack's first screen.

open as a page

In Expo Router, what does rendering <Redirect href> do, and how does it differ from calling router.replace directly in a screen?

level: middleimportance: should knowfreq 32%

basics

~20 s

Redirect renders nothing and calls router.replace(href) when its screen gains focus, so the target takes the current history entry; calling router.replace yourself needs the same timing, in a focus effect or handler, never during render.

open as a page

With Expo Router API routes, how do you add a waitlist signup endpoint, and what keeps its API key out of the app bundle?

level: middleimportance: should knowfreq 35%

basics

~20 s

Create src/app/api/waitlist+api.ts exporting a POST(request) function that returns a Response, with web.output set to server. Code only API routes import runs on the server and is stripped from the client bundle, so the key stays server-side.

open as a page

In an Expo Router project, what do the web.output values single, static and server produce, and which suits a marketing site?

level: middleimportance: should knowfreq 40%

basics

~20 s

single exports one index.html for a client-rendered SPA, static renders an HTML file per route at export time, and server adds a server bundle for API routes. A marketing site wants static, or server once it has its own endpoints.

open as a page

In an Expo SDK 57 app on Expo Router, why does importing ThemeProvider from '@react-navigation/native' fail to bundle, and what replaces it?

level: seniorimportance: should knowfreq 30%

basics

~10 s

Since SDK 56 expo-router ships its own copy of the React Navigation code and Expo CLI's Metro resolver rejects @react-navigation/* imports from app code; import ThemeProvider from 'expo-router/react-navigation' (or 'expo-router') instead.

open as a page

In an Expo Router app, how do you restore a saved session at launch so the sign-in screen never flashes before the account area appears?

level: seniorimportance: should knowfreq 32%

basics

~20 s

Keep the native splash screen up with SplashScreen.preventAutoHideAsync() until an auth provider in the root layout has read the stored session, then hide it. Stack.Protected guards read that provider, so the first visible frame already has the right screens.

open as a page

In an Expo Router pharmacy app, how would you return the user to the cart after sign-in, including when sign-in was opened by a cold-start deep link?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Open sign-in with an href object carrying a returnTo param, validate that it is an internal path, and on success call router.dismissTo(returnTo), which pops back to the cart if it is in history and otherwise replaces sign-in with it.

open as a page

In Expo Router, why does a deep link straight to a modal route leave no screen behind it, and what does unstable_settings.anchor change?

level: middleimportance: nice to knowfreq 18%

basics

~20 s

A deep link builds the stack from the URL alone, so it holds only the modal. Exporting unstable_settings = { anchor: 'index' } from that stack's layout keeps index rendered underneath, and the same anchor is where guarded deep links land.

open as a page

In an Expo Router web build, what is src/app/+html.tsx for, and why can it not read window or host context providers?

level: middleimportance: nice to knowfreq 15%

basics

~20 s

+html.tsx customises the root HTML document that wraps every statically rendered page. It runs only in Node.js at render time, so window is undefined there, and providers belong in the root layout, which runs in the app.

open as a page

With Expo Router, a cold-start deep link to /cookbook/desserts opens with no back button; how does exporting unstable_settings.initialRouteName from a _layout fix that?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

A deep link builds navigation state from the URL alone, so the cookbook stack holds only desserts; exporting unstable_settings = { initialRouteName: 'index' } from cookbook/_layout.tsx makes Expo Router place index beneath it, restoring a back target.

open as a page

In an Expo Router project, what does enabling experiments.typedRoutes check at compile time, and how do you keep it working in CI?

level: seniorimportance: nice to knowfreq 28%

basics

~10 s

With experiments.typedRoutes, Expo CLI generates route types from src/app, so TypeScript rejects hrefs to missing routes, dynamic hrefs with wrong params and relative paths; CI must run npx expo customize tsconfig.json before type checking.

open as a page

In an Expo Router app, fetch('/api/waitlist') works in development but fails in the native release build; why, and how do you fix it?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

In development relative URLs resolve to the Expo dev server, which runs API routes. A release build has no dev server, so the server must be deployed and its URL set as the expo-router plugin's origin before building.

open as a page