Expo Router: rotas, layouts e parâmetros no app
Aprenda Expo Router no React Native com src/app, Stack, tabs, Link, router.push e rota dinâmica tipada, entendendo o sistema de arquivos.
Expo Router transforma arquivos dentro de src/app em rotas do aplicativo.
index.tsx representa o endereço do diretório, [id].tsx recebe um segmento
dinâmico e _layout.tsx organiza as telas filhas. Você navega por caminhos e
parâmetros, sem manter uma lista separada de nomes de tela.
Nesta lição, a gente monta uma pilha, um grupo de abas e uma rota de detalhe. Se você ainda está decidindo o ambiente, veja antes Expo Go e development builds.
O prédio, seus endereços e a planta de cada andar
Imagine um prédio. O diretório é um andar, cada arquivo comum é uma sala e o
nome na porta faz parte do endereço. _layout.tsx é a planta que define como
as salas daquele trecho se conectam: pilha, abas ou outro navegador.
O nome técnico é file-based routing, roteamento baseado em arquivos. No
mapa, src/app/index.tsx atende /, src/app/perfil.tsx atende /perfil e
src/app/produtos/[id].tsx atende /produtos/42, expondo 42 como parâmetro.
O limite da comparação é que pastas entre parênteses, como (tabs), organizam
layouts sem necessariamente entrar na URL. Tecnicamente, são route groups:
ajudam a estruturar navegação e podem ter layouts próprios, mas o nome do grupo
não vira segmento público.
O guia de Expo ajuda a posicionar o Router: ele é uma camada de navegação do projeto Expo, construída sobre primitivas compatíveis do ecossistema React Navigation. Você continua lidando com histórico, estado de tela e links do sistema operacional. A convenção de arquivos não elimina essas decisões; ela oferece um mapa único para endereço, layout e deep link.
Confirme a raiz usada pelo template atual
Projetos atuais do Expo Router usam src/app por padrão. A estrutura mínima é:
src/
└── app/
├── _layout.tsx
├── index.tsx
└── habitos/
└── [id].tsxTodo arquivo de tela sob src/app participa do roteamento. Coloque componentes,
hooks e serviços em src/components, src/hooks e src/services. Um arquivo
src/app/helpers.ts não fica invisível: ele pode virar uma rota acidental.
No package.json, a integração usa a entrada do Router:
{
"main": "expo-router/entry"
}O template já prepara plugin e dependências. Em um projeto existente, siga a
instalação oficial completa; copiar apenas main não instala peer dependencies
nem configura deep linking.
O layout raiz define a pilha
Crie src/app/_layout.tsx:
import { Stack } from 'expo-router';
export default function RootLayout() {
return (
<Stack>
<Stack.Screen name="index" options={{ title: 'Meus hábitos' }} />
<Stack.Screen name="habitos/[id]" options={{ title: 'Detalhes' }} />
</Stack>
);
}Uma Stack empilha telas. push acrescenta uma, e voltar remove o topo. O
arquivo de rota declara a existência da tela; Stack.Screen apenas ajusta
opções como título e apresentação. Não importe HomeScreen nem
HabitDetailScreen aqui.
No Expo Router do SDK atual, prefira APIs reexportadas por expo-router. A
documentação do SDK 56 em diante alerta contra importar implementações de
@react-navigation/* para misturar diretamente com o roteador, pois as versões
internas precisam permanecer alinhadas.
Navegue com Link quando a ação é um endereço
Na tela inicial, Link expressa uma navegação declarativa:
import { Link } from 'expo-router';
import { StyleSheet, Text, View } from 'react-native';
export default function HomeScreen() {
return (
<View style={styles.screen}>
<Text style={styles.title}>Meus hábitos</Text>
<Link
href={{ pathname: '/habitos/[id]', params: { id: 'agua' } }}
style={styles.link}
>
Abrir “Beber água”
</Link>
</View>
);
}
const styles = StyleSheet.create({
screen: { flex: 1, gap: 16, padding: 24 },
title: { fontSize: 28, fontWeight: '700' },
link: { color: '#2563eb', fontSize: 17 },
});Use Link quando existe um destino que faz sentido como link. Para navegar
depois de salvar um formulário ou concluir uma autenticação, use o objeto
router, porque a mudança depende de lógica imperativa.
import { router } from 'expo-router';
async function saveAndOpen(id: string, saveHabit: () => Promise<void>) {
await saveHabit();
router.replace({ pathname: '/habitos/[id]', params: { id } });
}replace troca a entrada atual; push acrescentaria outra. Em login, replace
costuma impedir que “voltar” retorne à tela de autenticação. Isso é uma decisão
de histórico, não apenas duas grafias para abrir a mesma página.
Prefira parâmetros pequenos e serializáveis. Passar o objeto inteiro de um
produto pela navegação cria duas fontes de verdade e dificulta abrir a tela por
um link externo. Passe o id e carregue o registro no destino. Assim
/habitos/agua continua significativo depois de reiniciar o app, e a tela pode
tratar corretamente um item removido.
Leia e valide o parâmetro dinâmico
Crie src/app/habitos/[id].tsx:
import { Stack, useLocalSearchParams } from 'expo-router';
import { Text, View } from 'react-native';
export default function HabitDetailScreen() {
const { id } = useLocalSearchParams<{ id?: string | string[] }>();
const safeId = Array.isArray(id) ? id[0] : id;
if (!safeId) return <Text>Hábito não informado.</Text>;
return (
<View>
<Stack.Screen options={{ title: `Hábito ${safeId}` }} />
<Text testID="habit-id">Identificador: {safeId}</Text>
</View>
);
}O genérico descreve possibilidades para o compilador, mas a URL continua sendo
entrada externa. Um parâmetro pode faltar, repetir ou carregar texto inesperado.
Converta números com Number, verifique Number.isInteger e busque dados com
tratamento de ausência antes de confiar nele.
O hook local acompanha os parâmetros da rota focada. Existe também leitura global, útil em casos específicos de navegação, mas capaz de atualizar telas fora de foco. Comece local para evitar renderizações sem necessidade.
Use grupos para abas sem poluir a URL
Para duas abas, reorganize os arquivos:
src/app/
├── _layout.tsx
├── (tabs)/
│ ├── _layout.tsx
│ ├── index.tsx
│ └── perfil.tsx
└── habitos/
└── [id].tsxO layout das abas fica em src/app/(tabs)/_layout.tsx:
import { Tabs } from 'expo-router';
export default function TabsLayout() {
return (
<Tabs>
<Tabs.Screen name="index" options={{ title: 'Hoje' }} />
<Tabs.Screen name="perfil" options={{ title: 'Perfil' }} />
</Tabs>
);
}O endereço da primeira aba continua /, não /(tabs). O grupo permite que a
rota de detalhe fique fora da barra de abas, mas dentro da Stack raiz. Visualize
como plantas aninhadas: a Stack raiz contém o grupo; o grupo contém suas abas.
Reconheça um caminho que não existe
Este destino referencia um arquivo ausente:
<Link href="/configuracoes">Configurações</Link>Em rotas tipadas, o TypeScript pode rejeitar o caminho durante a checagem. Sem
tipagem estrita, a navegação cai na rota +not-found. Crie o arquivo se a tela
é real ou corrija o href; não adicione uma página vazia apenas para silenciar o
erro.
Você pode conferir o mapa gerado sem publicar:
npx expo export --platform all --output-dir dist-proofNo projeto executado, a parte relevante foi:
Essa saída é a prova de que Router encontrou e empacotou os caminhos. Não prova que todos os botões levam ao destino correto; testes de interação e validação em dispositivo completam a conferência.
Missão: projete uma navegação que sobreviva a um link direto
Crie /habitos/[id] de modo que abrir o endereço diretamente funcione, mesmo
sem passar pela tela inicial. Mostre carregamento, item ausente e sucesso. Use
push para entrar pelo cartão e replace apenas quando quiser apagar a tela
anterior do histórico.
Mapa esperado:
/ lista
/perfil aba de perfil
/habitos/agua detalhe encontrado
/habitos/xyz estado “não encontrado”Teste também voltar, recarregar a web no detalhe e abrir um id inválido. Depois avance para config plugins e prebuild, onde a árvore JavaScript passa a gerar configuração Android e iOS.
Dúvidas e comentários
Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.
Entrar para perguntarÉ o mesmo login gratuito dos cursos.
Nenhuma dúvida por aqui ainda — a primeira pode ser a sua.
Todo o código deste artigo foi executado em Node 24.16.0, Expo SDK 57.0.15, Expo Router 57.0.15, React Native 0.86.2, React 19.2.3 e TypeScript 6.0.3, e as saídas exibidas são as reais — como produzimos este conteúdo.
Fontes consultadas
- Expo Router — Introduction — docs.expo.dev
- Expo Router — Core concepts — docs.expo.dev
- Expo Router — Navigation layouts — docs.expo.dev
- Expo Router — Navigation — docs.expo.dev


