Expo Router no React Native: Guia Completo de Navegação Baseada em Arquivos

Dominar o Expo Router para React Native com este guia cobrindo roteamento baseado em arquivos, layouts, rotas dinâmicas, navegação tipada e padrões avançados como modais e tabs.

Sistema de navegacao baseado em arquivos Expo Router para aplicacoes moveis React Native

O Expo Router traz roteamento baseado em arquivos para o React Native, substituindo a configuração manual de navegação por uma abordagem baseada em convenções inspirada no Next.js. A partir do SDK Expo 57 e do Expo Router, construir navegação multiplataforma para Android, iOS e web requer apenas criar arquivos no diretório correto.

Configuração Rápida

Novos projetos Expo já vêm com o Expo Router pré-configurado. Execute npx create-expo-app@latest --template default para começar com roteamento baseado em arquivos pronto para uso. Projetos existentes podem adicioná-lo instalando expo-router e atualizando o ponto de entrada.

Como funciona o roteamento baseado em arquivos no Expo Router

Cada arquivo dentro do diretório app se torna automaticamente uma rota. O caminho do arquivo mapeia diretamente para o caminho da URL, eliminando a necessidade de uma configuração de navegação centralizada. Um arquivo em app/settings.tsx cria uma rota /settings, enquanto app/profile/edit.tsx mapeia para /profile/edit.

Esta abordagem oferece três vantagens principais sobre a configuração tradicional do React Navigation:

  • Zero configuração: as rotas existem no momento em que o arquivo é criado
  • Deep linking automático: cada tela obtém uma URL, habilitando compartilhamento e testes
  • Navegação tipada: TypeScript sabe quais rotas existem em tempo de compilação
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 },
})

O componente Link lida com a navegação em todas as plataformas. Na web, renderiza uma tag âncora com atributos href apropriados para SEO. Em plataformas nativas, dispara navegação baseada em stack.

Estrutura do projeto e arquivos de layout

O Expo Router usa arquivos _layout.tsx para definir contêineres de navegação. Cada diretório pode ter seu próprio layout, criando hierarquias de navegação aninhadas. O layout raiz envolve toda a aplicação, enquanto layouts aninhados controlam seções específicas.

Uma estrutura de projeto típica se apresenta assim:

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

Grupos de rotas, diretórios envolvidos em parênteses, organizam arquivos sem afetar a URL. O diretório (tabs) acima cria um navegador de abas, mas as URLs permanecem /home, /search e /profile em vez de /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>
  )
}

O layout raiz também serve como o local para carregar fontes, inicializar providers e configurar ajustes globais.

Construindo navegação por abas com Expo Router

A navegação por abas requer um arquivo _layout.tsx dentro de um grupo de rotas. O Expo Router fornece tanto NativeTabs para experiências de abas específicas por plataforma quanto o componente padrão Tabs para a maioria dos casos de uso. Desde o SDK 56, Native Tabs suporta a propriedade disabled para prevenir seleção de abas enquanto as mantém visíveis.

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>
  )
}

Cada arquivo de tela de aba exporta um componente React padrão. O ícone da barra de abas, rótulo e badge são configurados através da prop options no layout.

Rotas dinâmicas e parâmetros de rota

Segmentos dinâmicos usam colchetes no nome do arquivo. Um arquivo chamado [id].tsx corresponde a qualquer segmento único, enquanto [...slug].tsx captura todos os segmentos restantes.

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' },
})

Navegar para /product/42 renderiza esta tela com id definido como "42". O hook useLocalSearchParams fornece acesso tipado a todos os parâmetros de rota.

Para rotas catch-all, [...slug].tsx captura segmentos de caminho inteiros:

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('/')} />
}

Pronto para mandar bem nas entrevistas de React Native?

Pratique com nossos simuladores interativos, flashcards e testes tecnicos.

Rotas tipadas para segurança em tempo de compilação

O Expo Router gera tipos de rotas automaticamente quando typed routes está habilitado. Isso detecta links quebrados em tempo de compilação em vez de em tempo de execução.

Habilitar rotas tipadas no app.json:

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

Uma vez habilitado, a prop href no Link e o argumento para router.push() só aceitam strings de rotas válidas:

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')
}

Rotas tipadas funcionam bem com useLocalSearchParams. Os tipos gerados garantem que os nomes dos parâmetros correspondam entre a definição da rota e o componente consumidor, prevenindo bugs sutis que só aparecem ao navegar para uma tela específica.

Telas modais e opções de apresentação

Modais no Expo Router são telas regulares configuradas com presentation: 'modal' no layout. Esta abordagem mantém a convenção baseada em arquivos intacta: um modal é apenas outra rota.

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,
  },
})

Navegar para /create-post dispara a apresentação modal. A chamada router.back() a fecha, retornando à tela anterior no stack.

Além do componente Link, o Expo Router fornece uma API imperativa através do objeto router. Isso lida com navegação disparada por lógica de negócio em vez de interações do usuário.

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
}

A distinção entre router.push e router.replace importa para fluxos de autenticação no React Native. Após um login bem-sucedido, router.replace('/dashboard') previne que o usuário navegue de volta para a tela de login.

Navegação vs. Redirecionamento

router.replace() substitui a entrada atual no histórico de navegação. Para guardas de autenticação que redirecionam usuários não autenticados, usar <Redirect href="/login" /> dentro do render do componente: dispara durante a fase de render e funciona corretamente com server-side rendering na web.

Middleware e proteção de rotas

O Expo Router fornece middleware de servidor para lógica em nível de rota. O arquivo +middleware.ts intercepta requisições antes que cheguem ao componente da rota, habilitando verificações de autenticação, redirecionamentos e manipulação de headers.

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
}

Em plataformas nativas onde o middleware de servidor não executa, a proteção de rotas depende de guardas do lado do cliente. Um padrão comum envolve layouts protegidos com uma verificação de autenticação:

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 />
}
Consciência de Plataforma

O middleware de servidor executa apenas na web com server-side rendering. Para aplicativos nativos, sempre implementar guardas de rota do lado do cliente em componentes de layout. Combinar ambas as abordagens garante proteção consistente em todas as plataformas.

Migração SDK 56: Fork do React Navigation

A partir do SDK Expo 56, o Expo Router não depende mais dos pacotes @react-navigation/*. Se o projeto importa diretamente do React Navigation, atualizar os imports:

Pacote OriginalNova Localização
@react-navigation/nativeexpo-router/react-navigation
@react-navigation/stackexpo-router/js-stack
@react-navigation/bottom-tabsexpo-router/js-tabs

Executar o codemod para atualizar imports automaticamente:

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

A API em tempo de execução permanece inalterada. Apenas os especificadores de módulo foram movidos. Bibliotecas de terceiros que ainda importam de @react-navigation/core funcionam automaticamente porque o Expo CLI reescreve esses imports durante o bundling.

Fontes

O que os entrevistadores esperam sobre Expo Router

  • O Expo Router mapeia automaticamente cada arquivo no diretório app para uma rota, eliminando configuração manual de navegação
  • Layouts definidos através de arquivos _layout.tsx criam hierarquias de navegação para stacks, tabs e drawers
  • Rotas dinâmicas com [param].tsx e rotas catch-all com [...slug].tsx lidam com navegação parametrizada com suporte completo de TypeScript
  • Rotas tipadas detectam links de navegação quebrados em tempo de compilação quando habilitadas no app.json
  • Telas modais, navegação programática via router.push/replace/back, e middleware de servidor completam o toolkit de roteamento
  • Grupos de rotas usando diretórios com parênteses organizam código sem afetar URLs
  • Guardas do lado do cliente em layouts lidam com autenticação no nativo, enquanto middleware de servidor cobre web
  • Desde o SDK 56, imports de @react-navigation/* devem ser atualizados para equivalentes de expo-router/*

Para mais sobre arquitetura React Native e preparação para entrevistas, consultar o módulo de módulos nativos.

Comece a praticar!

Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.

Desafio do dia

Você saberia encontrar o bug em React Native?

Um trecho real, um bug escondido, uma tentativa por dia. Sem conta para testar.

Anthony Fillion-Maillet

Escrito por

Anthony Fillion-Maillet

Fundador da SharpSkill

Desenvolvedor fullstack há mais de 10 anos. Dirige a SharpSkill e responde por tudo o que é publicado aqui.

Atualizado em 6 de outubro de 2026

Tags

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

Compartilhar

Artigos relacionados