skip to content

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%

answer

  1. parentheses hide a folder from the URL
  2. index is the folder's default route
  3. launch resolves the / URL
  4. put index.tsx inside (tabs)
  5. group name is still a route name

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.

solid answer

~40 s

In Expo Router a **route group** is a folder whose name is wrapped in parentheses, like `src/app/(tabs)`. It can have its own `_layout.tsx` and therefore its own navigator, but its name never appears in the URL, so `src/app/(tabs)/saved.tsx` is `/saved`. An **`index.tsx`** file is the default route of its folder: `cookbook/index.tsx` answers `/cookbook`. No code setting picks the launch screen; at launch Expo Router resolves the `/` URL, so putting the tabs in `(tabs)` with the Recipes tab as `(tabs)/index.tsx` makes `/` land inside the tab navigator. Inside the parent layout the group is still addressed by its folder name, so the root stack styles it with `<Stack.Screen name="(tabs)" />`.

code

tsx · 13 lines
tsx
// src/app/_layout.tsx
// Files: (tabs)/_layout.tsx, (tabs)/index.tsx, (tabs)/saved.tsx, settings.tsx
import { Stack } from 'expo-router';

export default function RootLayout() {
  return (
    <Stack>
      {/* Route name keeps the parentheses; the URL does not. */}
      <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
      <Stack.Screen name="settings" options={{ title: 'Settings' }} />
    </Stack>
  );
}

go deeper

for a junior

Recall that parentheses keep a folder out of the URL and that index.tsx is a folder's default route, so (tabs)/index.tsx answers /.

for a middle

Explain how launch resolves / to the first matching index.tsx, why the root stack names the group (tabs), and why the group's header is usually hidden.

for a senior

Show you plan URLs as a contract: groups keep URLs flat while swapping layouts, and two groups must never claim the same path.

for a principal

Argue how a team should carve a large app into groups so layout changes never force URL changes that break shipped deep links and marketing links.

## Static segments and index files In Expo Router a plain folder or file name is a **static segment**: `src/app/cookbook/desserts.tsx` is `/cookbook/desserts`. An **`index.tsx`** file is the **default route of its folder**, exactly as `index.html` is on a web server: - `src/app/cookbook/index.tsx` answers `/cookbook`; - `src/app/index.tsx` answers `/`; - a folder with no `index.tsx` has no page at its own path, so `/cookbook` would fall through to the not-found screen. ## Route groups: folders the URL ignores A folder whose name is wrapped in **parentheses** is a **route group**. It exists to shape the navigator tree, not the URL: - `src/app/(tabs)/saved.tsx` is served at `/saved`; the `(tabs)` segment is invisible. - The group can hold its own `_layout.tsx`, so everything in it shares one navigator (a tab bar) without every URL carrying a `/tabs` prefix. - Several groups can sit side by side, for example `(tabs)` for the main app and another group for onboarding screens, each with a different layout, while all their URLs stay flat. A group must be a **folder**. A file named `(tabs).tsx` is rejected with an error that routes cannot end with the group syntax. ## Choosing the first screen Expo Router has **no setting in code that chooses the launch screen** (a layout's `unstable_settings.initialRouteName` only adds a screen beneath a deep-linked one). When the app opens (or a browser loads the site root) it resolves the **`/` URL** and renders the first `index.tsx` that matches it. Putting a tab bar first is therefore a file-placement decision: 1. Create `src/app/(tabs)/_layout.tsx` returning a `Tabs` navigator. 2. Put the default tab in `src/app/(tabs)/index.tsx`; because the group adds no segment, it matches `/`. 3. Do not also keep a `src/app/index.tsx`, so `/` has one obvious owner. 4. In the root `_layout.tsx`, render a `Stack` and hide its header for the group with `<Stack.Screen name="(tabs)" options={{ headerShown: false }} />`, so the tabs are not drawn under a second header. The root stack can then push screens that sit **outside** the group, such as `src/app/settings.tsx`, over the whole tab bar, which is the usual "tabs inside a stack" shape. ## Groups are invisible in URLs but not in layouts The parentheses disappear from the URL, **not** from the route names a layout sees. In the root layout the tab navigator's route name is `(tabs)`, which is what `Stack.Screen name` must match. When the root layout lists no screens at all, Expo Router orders the remaining routes itself, and `index` routes and group routes sort ahead of other static routes, so `(tabs)` comes first. | File | URL | Route name in the root layout | |---|---|---| | `src/app/(tabs)/index.tsx` | `/` | inside `(tabs)` | | `src/app/(tabs)/saved.tsx` | `/saved` | inside `(tabs)` | | `src/app/cookbook/index.tsx` | `/cookbook` | `cookbook/index` | | `src/app/settings.tsx` | `/settings` | `settings` | ## Reading a tree with groups A quick way to read any Expo Router tree is to ask two separate questions of each folder: - **Does it add a URL segment?** Plain folders do; folders in parentheses do not. - **Does it add a navigator?** Only if it contains a `_layout.tsx`; a plain folder without one simply hoists its files into the parent navigator. The two answers are independent. `(tabs)` adds a navigator but no segment, `cookbook/` without a layout adds a segment but no navigator, and `cookbook/` with a layout adds both. Most confusion about "why is this URL different from the folder structure" disappears once a candidate separates URL shape from navigator shape, which is exactly what route groups make possible. ## Pitfalls interviewers probe - **Two routes, one URL.** A group removes a segment, so `src/app/(tabs)/saved.tsx` and a `src/app/saved.tsx` both claim `/saved`; keep URLs unique across groups. - **Expecting `/tabs/saved`.** Links written with the group name in them do not match; the URL is `/saved`. - **Treating a group as access control.** A group only decides which layout wraps a route. Hiding signed-in screens needs a guard in the layout, which is a separate mechanism. - **Forgetting the header.** A `Tabs` group inside a root `Stack` shows the stack's header above the tab screens' own headers unless `headerShown: false` is set on the group's screen. Getting groups right is what lets the recipe app open on its tab bar at `/`, keep its URLs short, and still push settings or a recipe collection above the tabs.

  • If src/app/cookbook/ has desserts.tsx but no index.tsx, what does /cookbook show in Expo Router?
    There is no route at `/cookbook`, so the URL is unmatched and Expo Router renders the not-found screen, either the generated default or your `+not-found.tsx`. Adding `cookbook/index.tsx` gives the folder a default page.
  • How would you give onboarding screens a different layout from the tabs without changing their URLs?
    Put them in a sibling group such as `src/app/(onboarding)/` with its own `_layout.tsx`, for example a `Stack` with no header. The URLs stay flat (`/welcome`, not `/onboarding/welcome`) while the two sets of screens get different navigators.

saying these in an interview costs you the question

  • The (tabs) folder adds a /tabs segment to every URL.
  • The first screen is set with an initialRouteName prop on the root navigator.
  • A route group can be a single file named (tabs).tsx.
  • Stack.Screen should name the group tabs without parentheses.
  • Putting routes in a group protects them from unauthenticated users.