Expo Router pour React Native : Guide Complet de la Navigation par Fichiers

Maîtriser Expo Router pour React Native avec ce guide couvrant le routage par fichiers, les layouts, les routes dynamiques, la navigation typée et les patterns avancés comme les modales et les onglets.

Système de navigation basé sur les fichiers Expo Router pour les applications mobiles React Native

Expo Router apporte le routage basé sur les fichiers à React Native, remplaçant la configuration manuelle de navigation par une approche conventionnelle inspirée de Next.js. Avec le SDK Expo 57 et Expo Router, la construction de la navigation multiplateforme pour Android, iOS et le web ne nécessite rien de plus que la création de fichiers dans le bon répertoire.

Mise en route rapide

Les nouveaux projets Expo intègrent Expo Router par défaut. La commande npx create-expo-app@latest --template default génère un projet préconfiguré avec le routage par fichiers. Pour les projets existants, il suffit d'installer le package expo-router et de mettre à jour le point d'entrée de l'application.

Fonctionnement du routage par fichiers dans Expo Router

Chaque fichier placé dans le répertoire app devient automatiquement une route. Le chemin du fichier correspond directement au chemin de l'URL, ce qui élimine toute nécessité d'un fichier de configuration centralisé. Un fichier app/settings.tsx crée la route /settings, tandis que app/profile/edit.tsx correspond à /profile/edit.

Cette approche offre trois avantages majeurs par rapport à la configuration traditionnelle avec React Navigation :

  • Zéro configuration : les routes existent dès la création du fichier
  • Deep linking automatique : chaque écran dispose d'une URL, facilitant le partage et les tests
  • Navigation typée : TypeScript connaît les routes existantes dès la compilation
app/index.tsxtypescript
import { View, Text, StyleSheet } from 'react-native'
import { Link } from 'expo-router'

export default function HomeScreen() {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Welcome</Text>
      {/* Link maps directly to file path */}
      <Link href="/settings" style={styles.link}>
        Open Settings
      </Link>
      <Link href="/profile/edit" style={styles.link}>
        Edit Profile
      </Link>
    </View>
  )
}

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', padding: 24 },
  title: { fontSize: 28, fontWeight: 'bold', marginBottom: 16 },
  link: { fontSize: 16, color: '#61DAFB', marginTop: 12 },
})

Le composant Link gère la navigation sur toutes les plateformes. Sur le web, il génère une balise ancre avec les attributs href appropriés pour le référencement. Sur les plateformes natives, il déclenche une navigation de type stack.

Structure du projet et fichiers de layout

Expo Router utilise des fichiers _layout.tsx pour définir les conteneurs de navigation. Chaque répertoire peut contenir son propre layout, créant ainsi des hiérarchies de navigation imbriquées. Le layout racine enveloppe l'ensemble de l'application, tandis que les layouts imbriqués contrôlent des sections spécifiques.

Voici une structure de projet typique :

text
app/
  _layout.tsx          # Root layout (Stack or custom)
  index.tsx            # Home screen (/)
  (tabs)/              # Tab group (parentheses = route group)
    _layout.tsx        # Tab navigator
    home.tsx           # /home tab
    search.tsx         # /search tab
    profile.tsx        # /profile tab
  settings/
    _layout.tsx        # Settings stack layout
    index.tsx          # /settings
    notifications.tsx  # /settings/notifications
    privacy.tsx        # /settings/privacy

Les groupes de routes, des répertoires entourés de parenthèses, organisent les fichiers sans affecter l'URL. Le répertoire (tabs) ci-dessus crée un navigateur par onglets, mais les URL restent /home, /search et /profile plutôt que /tabs/home.

app/_layout.tsxtypescript
import { Stack } from 'expo-router'

export default function RootLayout() {
  return (
    <Stack
      screenOptions={{
        headerStyle: { backgroundColor: '#1a1a2e' },
        headerTintColor: '#ffffff',
        headerTitleStyle: { fontWeight: '600' },
      }}
    >
      <Stack.Screen name="index" options={{ title: 'Home' }} />
      <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
      <Stack.Screen name="settings" options={{ title: 'Settings' }} />
    </Stack>
  )
}

Le layout racine sert également de point d'initialisation pour le chargement des polices, la configuration des providers et les réglages globaux.

Construction d'une navigation par onglets

La navigation par onglets nécessite un fichier _layout.tsx à l'intérieur d'un groupe de routes. Expo Router propose à la fois NativeTabs pour des expériences natives spécifiques à chaque plateforme et le composant standard Tabs pour la plupart des cas d'utilisation. Depuis le SDK 56, Native Tabs supporte la propriété disabled pour empêcher la sélection d'un onglet tout en le gardant visible.

app/(tabs)/_layout.tsxtypescript
import { Tabs } from 'expo-router'
import { Ionicons } from '@expo/vector-icons'

export default function TabLayout() {
  return (
    <Tabs
      screenOptions={{
        tabBarActiveTintColor: '#61DAFB',
        tabBarInactiveTintColor: '#888',
        tabBarStyle: {
          backgroundColor: '#1a1a2e',
          borderTopColor: '#2d2d44',
        },
      }}
    >
      <Tabs.Screen
        name="home"
        options={{
          title: 'Home',
          tabBarIcon: ({ color, size }) => (
            <Ionicons name="home" size={size} color={color} />
          ),
        }}
      />
      <Tabs.Screen
        name="search"
        options={{
          title: 'Search',
          tabBarIcon: ({ color, size }) => (
            <Ionicons name="search" size={size} color={color} />
          ),
        }}
      />
      <Tabs.Screen
        name="profile"
        options={{
          title: 'Profile',
          tabBarIcon: ({ color, size }) => (
            <Ionicons name="person" size={size} color={color} />
          ),
        }}
      />
    </Tabs>
  )
}

Chaque fichier d'écran d'onglet exporte un composant React standard. L'icône, le libellé et le badge de la barre d'onglets se configurent via la prop options du layout.

Routes dynamiques et paramètres de route

Les segments dynamiques utilisent des crochets dans le nom du fichier. Un fichier nommé [id].tsx correspond à un segment unique, tandis que [...slug].tsx capture tous les segments restants du chemin.

app/product/[id].tsxtypescript
import { View, Text, StyleSheet } from 'react-native'
import { useLocalSearchParams, Stack } from 'expo-router'

export default function ProductScreen() {
  // Extract the dynamic parameter from the URL
  const { id } = useLocalSearchParams<{ id: string }>()

  return (
    <View style={styles.container}>
      <Stack.Screen options={{ title: `Product ${id}` }} />
      <Text style={styles.heading}>Product Details</Text>
      <Text style={styles.id}>ID: {id}</Text>
    </View>
  )
}

const styles = StyleSheet.create({
  container: { flex: 1, padding: 24 },
  heading: { fontSize: 24, fontWeight: 'bold', marginBottom: 8 },
  id: { fontSize: 16, color: '#888' },
})

Accéder à /product/42 affiche cet écran avec la variable id définie sur "42". Le hook useLocalSearchParams fournit un accès typé à l'ensemble des paramètres de route.

Pour les routes attrape-tout, [...slug].tsx capture des segments de chemin entiers :

app/docs/[...slug].tsxtypescript
import { useLocalSearchParams } from 'expo-router'

export default function DocsScreen() {
  // /docs/getting-started/installation → slug = ['getting-started', 'installation']
  const { slug } = useLocalSearchParams<{ slug: string[] }>()

  return <DocViewer path={slug.join('/')} />
}

Prêt à réussir tes entretiens React Native ?

Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.

Typage des routes pour une sécurité à la compilation

Expo Router génère automatiquement les types de routes lorsque la fonctionnalité typed routes est activée. Cette vérification détecte les liens cassés à la compilation plutôt qu'à l'exécution.

Activation dans le fichier app.json :

json
{
  "expo": {
    "experiments": {
      "typedRoutes": true
    }
  }
}

Une fois activé, la prop href du composant Link et l'argument de router.push() n'acceptent que des chaînes correspondant à des routes valides :

app/checkout.tsxtypescript
import { router } from 'expo-router'

function handleCheckout(cartId: string) {
  // TypeScript validates this route exists
  router.push(`/product/${cartId}`)

  // This would cause a compile error if /nonexistent doesn't exist
  // router.push('/nonexistent')
}

Le typage des routes fonctionne particulièrement bien avec useLocalSearchParams. Les types générés garantissent la correspondance entre les noms de paramètres dans la définition de la route et ceux utilisés par le composant consommateur, prévenant ainsi les bugs subtils qui ne se manifestent qu'à la navigation vers un écran spécifique.

Écrans modaux et options de présentation

Les modales dans Expo Router sont des écrans classiques configurés avec presentation: 'modal' dans le layout. Cette approche respecte la convention du routage par fichiers : une modale reste simplement une route comme les autres.

app/_layout.tsxtypescript
import { Stack } from 'expo-router'

export default function RootLayout() {
  return (
    <Stack>
      <Stack.Screen name="index" />
      <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
      {/* Modal screen slides up from the bottom */}
      <Stack.Screen
        name="create-post"
        options={{
          presentation: 'modal',
          headerTitle: 'New Post',
        }}
      />
    </Stack>
  )
}
app/create-post.tsxtypescript
import { View, TextInput, Button, StyleSheet } from 'react-native'
import { router } from 'expo-router'
import { useState } from 'react'

export default function CreatePostModal() {
  const [title, setTitle] = useState('')

  const handleSubmit = () => {
    // Submit logic here
    router.back() // Dismiss the modal
  }

  return (
    <View style={styles.container}>
      <TextInput
        style={styles.input}
        placeholder="Post title"
        value={title}
        onChangeText={setTitle}
      />
      <Button title="Publish" onPress={handleSubmit} />
    </View>
  )
}

const styles = StyleSheet.create({
  container: { flex: 1, padding: 24 },
  input: {
    borderWidth: 1,
    borderColor: '#333',
    borderRadius: 8,
    padding: 12,
    fontSize: 16,
    marginBottom: 16,
  },
})

Accéder à /create-post déclenche la présentation modale. L'appel à router.back() la ferme et ramène l'utilisateur à l'écran précédent dans la pile de navigation.

Au-delà du composant Link, Expo Router fournit une API impérative via l'objet router. Celle-ci permet de gérer la navigation déclenchée par la logique métier plutôt que par une interaction directe de l'utilisateur.

typescript
import { router } from 'expo-router'

// Push a new screen onto the stack
router.push('/profile/settings')

// Replace the current screen (no back button)
router.replace('/login')

// Go back to the previous screen
router.back()

// Navigate with parameters
router.push({
  pathname: '/product/[id]',
  params: { id: '42', source: 'recommendations' },
})

// Check if going back is possible
import { useRouter } from 'expo-router'

function BackButton() {
  const router = useRouter()
  return router.canGoBack() ? (
    <Button title="Back" onPress={() => router.back()} />
  ) : null
}

La distinction entre router.push et router.replace est essentielle dans les flux d'authentification en React Native. Après une connexion réussie, router.replace('/dashboard') empêche l'utilisateur de revenir à l'écran de connexion via le bouton retour.

Navigation et redirection

router.replace() remplace l'entrée courante dans l'historique de navigation. Pour les gardes d'authentification qui redirigent les utilisateurs non connectés, il est préférable d'utiliser <Redirect href="/login" /> dans le rendu du composant. Ce dernier se déclenche pendant la phase de rendu et fonctionne correctement avec le rendu côté serveur sur le web.

Middleware et protection des routes

Expo Router fournit un middleware serveur pour la logique appliquée au niveau des routes. Le fichier +middleware.ts intercepte les requêtes avant qu'elles n'atteignent le composant de la route, permettant ainsi des vérifications d'authentification, des redirections et la manipulation des en-têtes HTTP.

app/+middleware.tstypescript
import { type MiddlewareRequest } from 'expo-router/server'

export function middleware(request: MiddlewareRequest) {
  const { pathname } = request.nextUrl

  // Protect dashboard routes
  const protectedPaths = ['/dashboard', '/settings', '/profile']
  const isProtected = protectedPaths.some(p => pathname.startsWith(p))

  if (isProtected) {
    const token = request.cookies.get('session')
    if (!token) {
      return Response.redirect(new URL('/login', request.url))
    }
  }

  return undefined // Continue to route
}

Sur les plateformes natives, le middleware serveur ne s'exécute pas. La protection des routes repose alors sur des gardes côté client. Le pattern le plus courant consiste à envelopper les layouts protégés d'une vérification d'authentification :

app/(authenticated)/_layout.tsxtypescript
import { Redirect, Stack } from 'expo-router'
import { useAuth } from '@/hooks/useAuth'

export default function AuthenticatedLayout() {
  const { isLoggedIn, isLoading } = useAuth()

  if (isLoading) return null
  if (!isLoggedIn) return <Redirect href="/login" />

  return <Stack />
}
Spécificités par plateforme

Le middleware serveur ne s'exécute que sur le web avec le rendu côté serveur. Pour les applications natives, il est impératif d'implémenter des gardes côté client dans les composants de layout. Combiner les deux approches garantit une protection cohérente sur l'ensemble des plateformes.

Migration SDK 56 : fork de React Navigation

À partir du SDK Expo 56, Expo Router ne dépend plus des packages @react-navigation/*. Si votre projet importe directement depuis React Navigation, les imports doivent être mis à jour :

Package originalNouvel emplacement
@react-navigation/nativeexpo-router/react-navigation
@react-navigation/stackexpo-router/js-stack
@react-navigation/bottom-tabsexpo-router/js-tabs

Exécutez le codemod pour mettre à jour les imports automatiquement :

bash
npx expo-codemod sdk-56-expo-router-react-navigation-replace src

L'API à l'exécution reste inchangée. Seuls les spécificateurs de modules ont été déplacés. Les bibliothèques tierces qui importent encore depuis @react-navigation/core fonctionnent automatiquement car Expo CLI réécrit ces imports lors du bundling.

Sources

Ce que les recruteurs attendent sur Expo Router

  • Expo Router associe automatiquement chaque fichier du répertoire app à une route, éliminant la configuration manuelle de navigation
  • Les layouts définis via les fichiers _layout.tsx créent des hiérarchies de navigation pour les stacks, onglets et drawers
  • Les routes dynamiques avec [param].tsx et les routes attrape-tout avec [...slug].tsx gèrent la navigation paramétrée avec un support TypeScript complet
  • Le typage des routes détecte les liens de navigation cassés à la compilation lorsque la fonctionnalité est activée dans app.json
  • Les écrans modaux, la navigation programmatique via router.push/replace/back et le middleware serveur complètent la boîte à outils du routage
  • Les groupes de routes par répertoires entre parenthèses organisent le code sans affecter les URL
  • Les gardes côté client dans les layouts gèrent l'authentification sur le natif, tandis que le middleware serveur couvre le web
  • Depuis le SDK 56, les imports depuis @react-navigation/* doivent être mis à jour vers les équivalents expo-router/*

Pour approfondir l'architecture React Native et la préparation aux entretiens, consultez le module sur les modules natifs.

Passe à la pratique !

Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.

Défi du jour

Tu saurais repérer le bug en React Native ?

Un vrai bout de code, un bug caché, une tentative par jour. Sans compte pour essayer.

Anthony Fillion-Maillet

Écrit par

Anthony Fillion-Maillet

Fondateur de SharpSkill

Développeur fullstack depuis plus de 10 ans. Il dirige SharpSkill et répond de tout ce qui y est publié.

Mis à jour le 6 octobre 2026

Tags

#react-native
#expo
#expo-router
#navigation
#mobile-development
#tutorial

Partager

Articles similaires