Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoIntermediáriocódigo testado

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.

Rodolfo Mori4 min de leitura

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 é:

text
src/
└── app/
    ├── _layout.tsx
    ├── index.tsx
    └── habitos/
        └── [id].tsx

Todo 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:

json
{
  "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:

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.

Na tela inicial, Link expressa uma navegação declarativa:

tsx
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.

tsx
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:

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:

text
src/app/
├── _layout.tsx
├── (tabs)/
│   ├── _layout.tsx
│   ├── index.tsx
│   └── perfil.tsx
└── habitos/
    └── [id].tsx

O layout das abas fica em src/app/(tabs)/_layout.tsx:

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:

tsx
<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:

bash
npx expo export --platform all --output-dir dist-proof

No projeto executado, a parte relevante foi:

Static routes (4) ├── / ├── /+not-found └── /habitos/[id]

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.

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.

text
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.

  • expo router
  • rotas expo
  • navegacao react native
  • stack
  • rota dinamica

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

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

  1. Expo Router — Introduction — docs.expo.dev
  2. Expo Router — Core concepts — docs.expo.dev
  3. Expo Router — Navigation layouts — docs.expo.dev
  4. Expo Router — Navigation — docs.expo.dev

Continue por aqui