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.

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.
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:
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:
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):
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:
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:
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:
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:
{
"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:
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:
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:
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.
Navegação Programática e a API do Router
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:
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.
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:
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:
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.
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.tsxcontrolam 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].tsxe catch-all com[...slug].tsxcapturam 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
routeroferece navegacao programatica compush,replace,backedismissAll - 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
Compartilhar
Artigos relacionados

React Native: Desenvolvimento completo de um aplicativo móvel em 2026
Guia completo para desenvolver aplicativos móveis iOS e Android com React Native. Da configuração inicial até a publicação nas lojas, todos os fundamentos necessários.

Angular Standalone Components: Guia Completo de Migração e Boas Práticas em 2026
Guia completo sobre Angular Standalone Components com migração passo a passo via CLI, roteamento com loadComponent, eliminação de NgModules, testes e ganhos de performance. Exemplos práticos com código.