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

Guia completo do Expo Router para React Native com navegação baseada em arquivos, rotas dinâmicas, tabs, modais, rotas tipadas e proteção de rotas. Exemplos práticos com código.

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

O Expo Router transformou a forma como aplicações React Native lidam com navegação ao trazer o conceito de roteamento baseado em arquivos — um padrão amplamente consolidado na web com frameworks como Next.js — para o ecossistema mobile. Em vez de configurar manualmente stacks de navegação com objetos e arrays, os desenvolvedores agora podem criar rotas simplesmente adicionando arquivos na pasta app/. Cada arquivo se torna automaticamente uma rota, e a estrutura de diretórios define a hierarquia de navegação. Essa abordagem reduz drasticamente o boilerplate, melhora a previsibilidade do código e unifica a experiência de desenvolvimento entre plataformas web, iOS e Android.

Configuração Rápida

Para iniciar um projeto com Expo Router já configurado, basta executar npx create-expo-app@latest --template tabs. O template inclui navegação por tabs, layouts aninhados e TypeScript pré-configurados. O Expo Router v4+ exige Expo SDK 53 ou superior.

Como Funciona o Roteamento Baseado em Arquivos no Expo Router

O princípio fundamental do Expo Router é simples: a estrutura de arquivos dentro do diretório app/ define todas as rotas da aplicação. Não existe um arquivo central de configuração de rotas. Cada arquivo .tsx ou .ts exporta um componente React que se torna a tela correspondente àquele caminho.

O arquivo index.tsx na raiz do diretório app/ representa a rota inicial da aplicação:

app/index.tsxtsx
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 Home</Text>
      <Link href="/profile" style={styles.link}>
        Go to Profile
      </Link>
      <Link href="/settings" style={styles.link}>
        Open Settings
      </Link>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
  title: { fontSize: 24, fontWeight: 'bold', marginBottom: 20 },
  link: { fontSize: 16, color: '#2563eb', marginTop: 10 },
});

O componente Link do Expo Router funciona de maneira análoga às tags <a> na web. Ele recebe uma prop href com o caminho de destino e realiza a navegação de forma declarativa. Ao contrário do React Navigation tradicional, onde seria necessário chamar navigation.navigate('Profile'), o Expo Router utiliza URLs como cidadãos de primeira classe. Essa abordagem traz benefícios imediatos: deep linking funciona automaticamente, e a mesma URL funciona tanto na web quanto no mobile.

Estrutura de Projeto e Arquivos de Layout

A organização de diretórios no Expo Router segue convenções claras. Pastas representam segmentos de rota, e arquivos _layout.tsx controlam como as telas filhas são renderizadas dentro de cada segmento.

Uma estrutura típica de projeto se apresenta da seguinte forma:

text
app/
├── _layout.tsx          # Root layout (Stack navigator)
├── index.tsx            # "/" route
├── about.tsx            # "/about" route
├── (tabs)/
│   ├── _layout.tsx      # Tab navigator layout
│   ├── index.tsx         # First tab ("/")
│   ├── explore.tsx       # "/explore" tab
│   └── profile.tsx       # "/profile" tab
├── settings/
│   ├── _layout.tsx       # Settings stack layout
│   ├── index.tsx          # "/settings"
│   └── notifications.tsx  # "/settings/notifications"
└── [id].tsx              # Dynamic route ("/:id")

O arquivo _layout.tsx na raiz define o navegador principal da aplicação. Esse layout encapsula todas as rotas e determina o tipo de navegação utilizado (Stack, Tabs ou Drawer):

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

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

Os grupos de rotas, identificados por parênteses como (tabs), permitem organizar arquivos sem afetar a URL final. Uma rota dentro de (tabs)/explore.tsx é acessada como /explore, e não como /(tabs)/explore. Essa funcionalidade permite agrupar rotas logicamente sem poluir a estrutura de URLs da aplicação.

Construindo Navegação por Tabs com Expo Router

A navegação por tabs é o padrão mais comum em aplicações mobile. O Expo Router implementa tabs através de um arquivo _layout.tsx dentro de um grupo de rotas, utilizando o componente Tabs:

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

export default function TabsLayout() {
  return (
    <Tabs
      screenOptions={{
        tabBarActiveTintColor: '#2563eb',
        tabBarInactiveTintColor: '#64748b',
        tabBarStyle: {
          backgroundColor: '#ffffff',
          borderTopWidth: 1,
          borderTopColor: '#e2e8f0',
        },
      }}
    >
      <Tabs.Screen
        name="index"
        options={{
          title: 'Home',
          tabBarIcon: ({ color, size }) => (
            <Ionicons name="home" size={size} color={color} />
          ),
        }}
      />
      <Tabs.Screen
        name="explore"
        options={{
          title: 'Explore',
          tabBarIcon: ({ color, size }) => (
            <Ionicons name="compass" size={size} color={color} />
          ),
        }}
      />
      <Tabs.Screen
        name="profile"
        options={{
          title: 'Profile',
          tabBarIcon: ({ color, size }) => (
            <Ionicons name="person" size={size} color={color} />
          ),
        }}
      />
    </Tabs>
  );
}

Cada Tabs.Screen corresponde a um arquivo dentro do grupo (tabs)/. A propriedade name deve coincidir exatamente com o nome do arquivo (sem a extensão). O Expo Router trata cada tab como uma rota independente com seu próprio histórico de navegação, permitindo que o usuário navegue para sub-rotas dentro de cada tab sem perder o estado das demais.

Rotas Dinâmicas e Parâmetros de Rota

Rotas dinâmicas permitem criar telas que respondem a parâmetros variáveis na URL. No Expo Router, basta nomear o arquivo com colchetes para criar um segmento dinâmico:

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

export default function ProductScreen() {
  const { id } = useLocalSearchParams<{ id: string }>();

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

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
  title: { fontSize: 24, fontWeight: 'bold' },
  id: { fontSize: 16, color: '#64748b', marginTop: 8 },
});

O hook useLocalSearchParams extrai os parâmetros da URL atual de forma tipada. Para rotas como /product/42, o valor de id retorna "42". Esse hook é local ao componente, garantindo que cada tela mantenha seus próprios parâmetros mesmo em cenários de navegação complexos.

Para capturar múltiplos segmentos de URL, o Expo Router suporta rotas catch-all utilizando a sintaxe de spread:

app/docs/[...slug].tsxtsx
import { useLocalSearchParams } from 'expo-router';
import { View, Text } from 'react-native';

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

  return (
    <View style={{ flex: 1, padding: 20 }}>
      <Text>Documentation Path: {slug?.join(' → ')}</Text>
    </View>
  );
}

A rota [...slug].tsx captura qualquer quantidade de segmentos após /docs/, retornando-os como um array de strings. Esse padrão é especialmente valioso para sistemas de documentação, blogs ou qualquer estrutura com hierarquia de profundidade variável.

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 oferece suporte nativo a rotas tipadas com TypeScript, gerando automaticamente tipos para todas as rotas da aplicação. Essa funcionalidade elimina erros de digitação em URLs e fornece autocompletar no editor de código.

Para habilitar rotas tipadas, basta configurar o app.json:

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

Com essa configuração ativa, o Expo Router gera um arquivo de tipos que mapeia todas as rotas disponíveis. Qualquer referência a uma rota inexistente gera um erro de compilação:

app/checkout.tsx — typed navigation exampletsx
import { Link } from 'expo-router';
import { View, Text, Pressable } from 'react-native';
import { router } from 'expo-router';

export default function CheckoutScreen() {
  const handleComplete = () => {
    // TypeScript validates this route exists at compile time
    router.push('/order-confirmation');
  };

  return (
    <View style={{ flex: 1, padding: 20 }}>
      <Text style={{ fontSize: 20, fontWeight: 'bold' }}>Checkout</Text>

      {/* TypeScript error if "/invalid-route" doesn't exist */}
      <Link href="/order-confirmation">Complete Order</Link>

      <Pressable onPress={handleComplete}>
        <Text>Process Payment</Text>
      </Pressable>
    </View>
  );
}

Essa camada de segurança de tipos se mostra particularmente relevante em projetos de grande porte, onde dezenas de telas e centenas de links tornam erros manuais praticamente inevitáveis. O TypeScript detecta rotas inválidas durante o desenvolvimento, antes mesmo de executar a aplicação.

Telas Modais e Opções de Apresentação

Modais são telas que se sobrepõem ao conteúdo existente, geralmente utilizadas para formulários de criação, confirmações ou detalhes rápidos. O Expo Router implementa modais através de opções de apresentação no layout:

app/_layout.tsx — with modal supporttsx
import { Stack } from 'expo-router';

export default function RootLayout() {
  return (
    <Stack>
      <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
      <Stack.Screen
        name="create-post"
        options={{
          presentation: 'modal',
          title: 'Create Post',
          headerStyle: { backgroundColor: '#f8fafc' },
        }}
      />
    </Stack>
  );
}

A tela correspondente ao modal recebe toda a funcionalidade padrão de uma rota, incluindo deep linking e parâmetros:

app/create-post.tsxtsx
import { View, TextInput, Pressable, Text, StyleSheet } from 'react-native';
import { router } from 'expo-router';
import { useState } from 'react';

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

  const handleSubmit = () => {
    // Save post logic here
    router.back(); // Dismiss modal
  };

  return (
    <View style={styles.container}>
      <TextInput
        placeholder="Post title"
        value={title}
        onChangeText={setTitle}
        style={styles.input}
      />
      <TextInput
        placeholder="Write your post..."
        value={body}
        onChangeText={setBody}
        style={[styles.input, styles.bodyInput]}
        multiline
      />
      <Pressable onPress={handleSubmit} style={styles.button}>
        <Text style={styles.buttonText}>Publish</Text>
      </Pressable>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, padding: 20, backgroundColor: '#ffffff' },
  input: {
    borderWidth: 1, borderColor: '#e2e8f0', borderRadius: 8,
    padding: 12, fontSize: 16, marginBottom: 12,
  },
  bodyInput: { height: 200, textAlignVertical: 'top' },
  button: {
    backgroundColor: '#2563eb', padding: 16,
    borderRadius: 8, alignItems: 'center',
  },
  buttonText: { color: '#ffffff', fontSize: 16, fontWeight: 'bold' },
});

A chamada router.back() fecha o modal e retorna à tela anterior. No iOS, o gesto de swipe down para fechar funciona automaticamente quando a apresentação é configurada como modal.

Além da navegação declarativa com o componente Link, o Expo Router disponibiliza uma API imperativa completa através do objeto router. Essa API permite navegar em resposta a eventos, resultados de chamadas assíncronas ou lógica condicional:

tsx
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('/dashboard');

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

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

// Reset the navigation state entirely
router.dismissAll();
router.replace('/');

A distinção entre router.push e router.replace merece atenção especial. O método push adiciona uma nova entrada ao histórico de navegação, permitindo que o usuário retorne com o botão voltar. Já o replace substitui a entrada atual, sendo ideal para fluxos de autenticação onde o retorno à tela de login após o login bem-sucedido seria indesejado.

Navegação vs Redirecionamento

O router.push e o router.replace operam no lado do cliente. Para redirecionamentos no lado do servidor (SSR/SSG na web), deve-se utilizar a função redirect do Expo Router. Em rotas puramente mobile, router.replace dentro de um useEffect atende à maioria dos cenários de redirecionamento.

Middleware e Proteção de Rotas

Proteger rotas contra acesso não autorizado é um requisito fundamental em aplicações reais. O Expo Router oferece dois mecanismos principais para isso: middleware (na web) e layouts de autenticação (cross-platform).

O middleware funciona exclusivamente em contextos web e permite interceptar requisições antes da renderização:

app/_middleware.tstypescript
import { redirect } from 'expo-router/server';

export function GET(request: Request) {
  const token = request.headers.get('authorization');

  if (!token) {
    return redirect('/login');
  }
}

Para uma abordagem cross-platform que funcione tanto na web quanto no mobile, o padrão recomendado envolve um layout de autenticação que verifica a sessão do usuário e redireciona quando necessário:

app/(authenticated)/_layout.tsxtsx
import { Redirect, Stack } from 'expo-router';
import { useAuth } from '@/hooks/useAuth';
import { ActivityIndicator, View } from 'react-native';

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

  if (isLoading) {
    return (
      <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
        <ActivityIndicator size="large" color="#2563eb" />
      </View>
    );
  }

  if (!user) {
    return <Redirect href="/login" />;
  }

  return (
    <Stack>
      <Stack.Screen name="index" options={{ title: 'Dashboard' }} />
      <Stack.Screen name="settings" options={{ title: 'Settings' }} />
    </Stack>
  );
}

Todas as rotas dentro do grupo (authenticated)/ passam automaticamente pela verificação de autenticação definida no layout. Se o usuário não estiver logado, o componente Redirect redireciona para a tela de login. Caso o estado de autenticação ainda esteja sendo carregado, um indicador de atividade evita flashes de conteúdo indesejados.

Atenção à Plataforma

O middleware (_middleware.ts) funciona exclusivamente na web. Para proteção de rotas em iOS e Android, os layouts com verificação de autenticação representam a abordagem recomendada. Essa distinção exige que aplicações multiplataforma implementem ambas as estratégias quando necessário.

Conclusao

O Expo Router consolidou-se como a solucao de referencia para navegacao em aplicacoes React Native modernas. Ao adotar o roteamento baseado em arquivos, o framework elimina camadas de configuracao manual e alinha o desenvolvimento mobile com as praticas ja estabelecidas no ecossistema web. Os principais aspectos abordados neste guia incluem:

  • O roteamento baseado em arquivos transforma a estrutura de diretorios em rotas automaticas, eliminando configuracao manual de navegacao
  • Arquivos _layout.tsx controlam a renderizacao das telas filhas e definem o tipo de navegacao (Stack, Tabs, Drawer)
  • Grupos de rotas com parenteses (tabs) organizam arquivos sem impactar a URL final
  • Rotas dinamicas com [id].tsx e catch-all com [...slug].tsx capturam parametros de URL de forma tipada
  • Rotas tipadas com TypeScript detectam erros de navegacao em tempo de compilacao
  • Modais sao implementados via opcoes de apresentacao no layout, com suporte completo a deep linking
  • A API router oferece navegacao programatica com push, replace, back e dismissAll
  • A protecao de rotas combina middleware (web) e layouts de autenticacao (cross-platform) para controle de acesso

Comece a praticar!

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

Tags

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

Compartilhar

Artigos relacionados