Expo Router v4 ile Dosya Tabanlı Yönlendirme: 2026 Kapsamlı Rehber

Expo Router v4 ile dosya tabanlı yönlendirmeyi sıfırdan öğrenin: kurulum, dinamik rotalar, typed routes, iç içe layout'lar, deep linking ve API rotaları — 2026 kapsamlı rehber, gerçek kod örnekleriyle.

Expo Router v4 Dosya Tabanlı Yönlendirme 2026

Expo Router v4, React Native ekosisteminde dosya tabanlı yönlendirmeyi resmî bir standart hâline getirdi. Açıkçası, web tarafında Next.js'in yıllardır sunduğu rahatlığı mobilde — hatta üç platformda (iOS, Android ve Web) aynı anda — yaşamak isteyenler için 2026'nın varsayılan tercihi artık tartışmasız Expo Router. Bu rehberde sıfırdan kuruluma, oradan da typed routes, dinamik rotalar, iç içe layout'lar, deep linking ve API rotaları gibi ileri seviye desenlere kadar her şeye birlikte bakacağız. Hepsi de gerçek kod örnekleriyle.

Expo Router v4 Nedir ve Neden Önemli?

Kısaca: Expo Router, app/ dizinindeki dosya yapınızı doğrudan navigasyon yapısına çeviren, görüş açısı (yani "opinionated") oldukça net bir yönlendirme katmanı. Altta motor olarak React Navigation'ı kullanıyor; ama üzerine deklaratif bir API, otomatik deep linking ve TypeScript tip üretimi ekliyor. SDK 55 ile gelen v4 sürümünde web tarafı nihayet birinci sınıf vatandaş hâline geldi — server components, statik render ve API rotaları artık üretim hazır.

2026 itibarıyla yeni bir Expo projesi açtığınızda Expo Router zaten varsayılan olarak kurulu geliyor. Yani manuel NavigationContainer kurmak, manuel createStackNavigator yazmak ya da deep linking konfigürasyonuyla saatlerce boğuşmak — bu günler büyük ölçüde geride kaldı. Ben de itiraf edeyim: ilk geçiş yaptığımda eski projedeki yüzlerce satırlık navigation config'i silmek garip bir tatmin yaşatmıştı.

Klasik React Navigation'a Göre Avantajları

  • Sıfır boilerplate: Dosya oluştur, rota hazır. Manuel kayıt, manuel ekran tanımı yok.
  • Otomatik deep linking: Her dosya yolu doğrudan bir URL ile eşleşir; push notification ve marketing bağlantıları kutudan çıkar çıkmaz çalışır.
  • Tip güvenliği: experiments.typedRoutes ile rota adlarındaki en ufak bir tipo bile derleme zamanında patlar.
  • Universal yapı: Aynı dosya hem mobilde bir ekran, hem de web'de bir URL.
  • Lazy bundling: Geliştirme modunda yalnızca açılan ekran derlenir; soğuk başlatma süresi ciddi şekilde düşer.

Gereksinimler ve Kurulum

Expo Router v4, Expo SDK 52 ve sonrasıyla çalışıyor. Ama en güncel özellikler için SDK 55+ kullanmanızı şiddetle öneriyorum. New Architecture (Fabric + TurboModules) ile tam uyumlu ve React Native 0.79+ üzerinde test edilmiş durumda.

Yeni Proje Oluşturma

npx create-expo-app@latest my-app --template default@sdk-55
cd my-app
npx expo start

Bu komut, Expo Router'ın kurulu olduğu ve app/ dizini hazır gelen bir şablon üretiyor. app.json içinde scheme alanı (yani deep link şeması) ve plugins dizisinde expo-router otomatik olarak tanımlı geliyor — yani siz hiçbir şey yapmadan başlayabilirsiniz.

Mevcut Bir Projeye Eklemek

npx expo install expo-router react-native-safe-area-context \
  react-native-screens expo-linking expo-constants expo-status-bar

Sonra package.json içindeki giriş noktasını şu şekilde güncelleyin:

{
  "main": "expo-router/entry"
}

Son olarak app.json'a plugin ekleyin ve bir scheme tanımlayın (deep linking için zorunlu, atlamayın):

{
  "expo": {
    "scheme": "myapp",
    "plugins": ["expo-router"],
    "experiments": {
      "typedRoutes": true
    }
  }
}

Dosya Tabanlı Yönlendirmenin Temel Mantığı

Mantık aslında çok basit: app/ dizini içindeki her dosya bir rotaya karşılık geliyor. index.tsx dizinin kök rotası, alt dizinler de iç içe URL'ler üretiyor. Özel _layout.tsx dosyaları ise rota değil — kendisinden sonra gelen ekranları sarmalayan navigator tanımı.

Pratik Bir Dosya Yapısı

app/
├── _layout.tsx              # Root layout — tüm uygulamayı sarar
├── index.tsx                # / (anasayfa)
├── +not-found.tsx           # 404 ekranı
├── (tabs)/                  # Grup — URL'de görünmez
│   ├── _layout.tsx          # Tab navigator
│   ├── home.tsx             # /home
│   ├── search.tsx           # /search
│   └── profile.tsx          # /profile
├── (auth)/                  # Auth grubu (kullanıcı görmüyor)
│   ├── _layout.tsx          # Auth stack
│   ├── login.tsx            # /login
│   └── register.tsx         # /register
├── product/
│   ├── _layout.tsx          # Ürün stack'i
│   ├── index.tsx            # /product
│   └── [id].tsx             # /product/123 (dinamik)
└── settings/
    ├── _layout.tsx
    ├── index.tsx            # /settings
    └── [...slug].tsx        # /settings/a/b/c (catch-all)

Parantez içindeki klasörler — yani (tabs), (auth) gibi olanlar — route group olarak adlandırılır. Bunlar URL'ye eklenmez; yalnızca aynı layout'u paylaşan ekranları gruplandırmak için var. Kullanıcının oturum durumuna göre farklı navigator'ları aynı seviyede tutmanın en temiz yolu bu, kabul etmek lazım.

Layout Dosyaları: Stack, Tabs ve Drawer

Her _layout.tsx dosyası, kendisinden sonraki rotaların hangi navigator ile sarılacağını belirler. En sık karşınıza çıkacak üç desen şunlar:

1. Root Stack Layout

// app/_layout.tsx
import { Stack } from "expo-router";

export default function RootLayout() {
  return (
    <Stack screenOptions={{ headerShown: false }}>
      <Stack.Screen name="(tabs)" />
      <Stack.Screen name="(auth)" options={{ presentation: "modal" }} />
      <Stack.Screen name="product/[id]" options={{ headerShown: true, title: "Ürün" }} />
    </Stack>
  );
}

2. Tab Layout

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

export default function TabsLayout() {
  return (
    <Tabs
      screenOptions={{
        tabBarActiveTintColor: "#0a7ea4",
        headerShown: false,
      }}
    >
      <Tabs.Screen
        name="home"
        options={{
          title: "Anasayfa",
          tabBarIcon: ({ color, size }) => (
            <Ionicons name="home" color={color} size={size} />
          ),
        }}
      />
      <Tabs.Screen
        name="search"
        options={{
          title: "Ara",
          tabBarIcon: ({ color, size }) => (
            <Ionicons name="search" color={color} size={size} />
          ),
        }}
      />
      <Tabs.Screen
        name="profile"
        options={{
          title: "Profil",
          tabBarIcon: ({ color, size }) => (
            <Ionicons name="person" color={color} size={size} />
          ),
        }}
      />
    </Tabs>
  );
}

3. Drawer Layout

Drawer kullanmak istiyorsanız önce @react-navigation/drawer ve react-native-gesture-handler kurulumunu halletmeniz gerekiyor:

npx expo install @react-navigation/drawer react-native-gesture-handler react-native-reanimated
// app/(drawer)/_layout.tsx
import { Drawer } from "expo-router/drawer";
import { GestureHandlerRootView } from "react-native-gesture-handler";

export default function DrawerLayout() {
  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <Drawer>
        <Drawer.Screen name="dashboard" options={{ drawerLabel: "Panel" }} />
        <Drawer.Screen name="reports" options={{ drawerLabel: "Raporlar" }} />
      </Drawer>
    </GestureHandlerRootView>
  );
}

Dinamik Rotalar ve Parametreler

Bir rota segmentini parametreye dönüştürmek için dosya adını köşeli parantez içine alıyorsunuz: [id].tsx gibi. Parametreyi okumak içinse useLocalSearchParams hook'unu kullanıyorsunuz. Hepsi bu kadar.

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

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

  return (
    <View style={{ flex: 1, padding: 16 }}>
      <Stack.Screen options={{ title: `Ürün #${id}` }} />
      <Text>Ürün ID: {id}</Text>
    </View>
  );
}

Catch-All (Sınırsız Derinlik)

[...slug].tsx deseni, kendisinden sonra gelen tüm segmentleri yakalar. CMS benzeri içerik yapıları, dokümantasyon siteleri ya da iç içe wiki sayfaları için biçilmiş kaftan.

// app/docs/[...slug].tsx
import { useLocalSearchParams } from "expo-router";

export default function DocsPage() {
  // /docs/getting-started/installation -> slug = ["getting-started", "installation"]
  const { slug } = useLocalSearchParams<{ slug: string[] }>();
  return <Text>Yol: {slug.join(" / ")}</Text>;
}

Opsiyonel Catch-All

[[...slug]].tsx ise hem boş hem de dolu yolları karşılar — yani aynı dosya kök rota gibi de davranabilir. Küçük bir detay ama dokümantasyon sitelerinde hayat kurtarıyor.

Typed Routes ile Tip Güvenli Navigasyon

Expo Router, app/ dizininizi tarayıp otomatik olarak TypeScript tipleri üretiyor. Yani yanlış yazılmış bir rota artık derleme hatası veriyor — runtime'da öğrenmek zorunda değilsiniz. Bu, benim için tek başına geçişe değecek bir özellikti.

Etkinleştirme

// app.json
{
  "expo": {
    "experiments": {
      "typedRoutes": true
    }
  }
}

npx expo start komutu çalıştığında .expo/types/router.d.ts dosyası üretiliyor ve tsconfig'e otomatik dahil oluyor.

Kullanım

import { Link, router } from "expo-router";

// ✅ Statik rota — tipo derleme zamanında yakalanır
<Link href="/profile">Profilim</Link>

// ✅ Dinamik rota — params zorunlu ve tip kontrollü
<Link href={{ pathname: "/product/[id]", params: { id: "42" } }}>
  Ürünü Gör
</Link>

// ❌ Derleme hatası — params eksik
<Link href="/product/[id]" />

// ✅ Imperative navigasyon
router.push({ pathname: "/product/[id]", params: { id: "42" } });
router.replace("/login");
router.back();

Önemli bir not: tip üretimi göreli yolları desteklemiyor. Her zaman mutlak yol kullanın (/about, ./about değil). Bu konuda bir saatlik bir hata ayıklama oturumundan sonra öğrenmek zorunda kalmamak için söylüyorum.

Otomatik Deep Linking

Expo Router, dosya yapısından deep link konfigürasyonunu otomatik üretiyor. scheme tanımladıysanız, myapp://product/42 bağlantısı doğrudan /product/[id] ekranını açar — ek konfigürasyon yok, ek kod yok.

Universal Links (iOS) ve App Links (Android)

Web URL'lerinin uygulamayı açmasını istiyorsanız, app.json içinde associatedDomains ve intentFilters tanımlamalısınız:

{
  "expo": {
    "ios": {
      "associatedDomains": ["applinks:myapp.com"]
    },
    "android": {
      "intentFilters": [
        {
          "action": "VIEW",
          "autoVerify": true,
          "data": [{ "scheme": "https", "host": "myapp.com" }],
          "category": ["BROWSABLE", "DEFAULT"]
        }
      ]
    }
  }
}

Korumalı Rotalar (Auth Flow)

Klasik bir senaryo: kullanıcı giriş yapmamışsa (auth) grubu, giriş yapmışsa (tabs) grubu render edilsin. Root layout içinde Stack.Protected (SDK 53+) ya da kendi koşullu render mantığınızla bunu kolayca sağlayabilirsiniz.

// app/_layout.tsx
import { Stack } from "expo-router";
import { useAuth } from "@/hooks/useAuth";

export default function RootLayout() {
  const { isAuthenticated, isLoading } = useAuth();

  if (isLoading) return null;

  return (
    <Stack screenOptions={{ headerShown: false }}>
      <Stack.Protected guard={isAuthenticated}>
        <Stack.Screen name="(tabs)" />
        <Stack.Screen name="product/[id]" />
      </Stack.Protected>
      <Stack.Protected guard={!isAuthenticated}>
        <Stack.Screen name="(auth)" />
      </Stack.Protected>
    </Stack>
  );
}

Stack.Protected kullanmıyorsanız (örn. eski SDK'da takılı kaldıysanız), Redirect bileşeni de işinizi görür:

import { Redirect } from "expo-router";

export default function ProtectedScreen() {
  const { isAuthenticated } = useAuth();
  if (!isAuthenticated) return <Redirect href="/login" />;
  return <DashboardContent />;
}

Modal ve Özel Sunum Stilleri

Bir ekranın modal olarak açılmasını istiyorsanız, layout'ta presentation seçeneğini ayarlamanız yeterli:

// app/_layout.tsx
<Stack>
  <Stack.Screen
    name="cart"
    options={{
      presentation: "modal",
      animation: "slide_from_bottom",
    }}
  />
</Stack>

iOS 18+ ile gelen formSheet sunumu da destekleniyor. Apple Maps tarzı yarım sayfalı sheet'ler için gerçekten harika çalışıyor:

options={{
  presentation: "formSheet",
  sheetGrabberVisible: true,
  sheetAllowedDetents: [0.5, 0.9],
}}

API Rotaları (Server Endpoints)

Expo Router v4, web tarafında server-side API rotaları da sunuyor — bu tarafı belki de en heyecan verici yenilik. app/ dizini içinde +api.ts uzantılı dosyalar HTTP handler'ı olarak çalışıyor. Yani basit backend'lerinizi aynı projede tutabiliyorsunuz.

// app/api/products/[id]+api.ts
export async function GET(request: Request, { id }: { id: string }) {
  const product = await db.products.findUnique({ where: { id } });
  return Response.json(product);
}

export async function DELETE(request: Request, { id }: { id: string }) {
  await db.products.delete({ where: { id } });
  return new Response(null, { status: 204 });
}

İstemci tarafından erişim ise standart bir fetch çağrısı:

const res = await fetch("/api/products/42");
const product = await res.json();

Ufak bir uyarı: API rotaları yalnızca web/server hedeflerinde çalışıyor. Mobil uygulamanın bu uç noktalara ulaşması için EXPO_PUBLIC_API_URL tanımlayıp tam URL kullanmanız gerekecek.

Performans İpuçları

  • Lazy bundling: Geliştirme modunda varsayılan olarak açık. Üretim için app.json içinde web.bundler: "metro" ayarıyla web tarafında da etkin hâle getirilebiliyor.
  • useFocusEffect kullanın: Pahalı işlemleri yalnızca ekran odaklandığında çalıştırın — basit gibi görünüyor ama çoğu ekipte unutuluyor.
  • Hafif _layout.tsx: Layout dosyaları her ekran değişiminde değerlendirilir; ağır mantığı buraya koymayın.
  • Pre-loading: Kritik rotaları router.prefetch("/product/42") ile önceden yükleyebilirsiniz.
  • Static rendering (web): SEO odaklı sayfalar için web.output: "static" ile statik HTML üretin.

React Navigation'dan Geçiş

Mevcut bir React Navigation projesini Expo Router'a taşımak istiyorsanız, izlenebilecek pratik bir yol haritası şöyle:

  1. expo-router ve bağımlılıklarını kurun, main alanını güncelleyin.
  2. Mevcut NavigationContainer + Stack.Navigator ağacınızı dosya yapısına çevirin — her ekran kendi dosyasına gidiyor.
  3. navigation.navigate("Product", { id }) çağrılarını router.push({ pathname: "/product/[id]", params: { id } }) ile değiştirin.
  4. useRoute().params yerine useLocalSearchParams kullanın.
  5. Manuel deep link konfigürasyonunu silin — Expo Router zaten otomatik üretiyor.
  6. Aşamalı geçiş istiyorsanız, klasik navigator'ı tek bir Expo Router ekranı içine sarmalayabilirsiniz. Birkaç projede bu yaklaşımla çok rahat geçtim, önerebilirim.

Yaygın Hatalar ve Çözümleri

"Unmatched Route" Hatası

Bir rota dosyası bulunamadığında +not-found.tsx ekranı render ediliyor. Bu dosyayı tanımladığınızdan emin olun, yoksa kullanıcı boş bir ekrana bakar:

// app/+not-found.tsx
import { Link, Stack } from "expo-router";
import { Text, View } from "react-native";

export default function NotFoundScreen() {
  return (
    <>
      <Stack.Screen options={{ title: "Bulunamadı" }} />
      <View>
        <Text>Bu sayfa mevcut değil.</Text>
        <Link href="/">Anasayfaya dön</Link>
      </View>
    </>
  );
}

Tipler Güncellenmiyor

Yeni bir rota eklediğinizde tipler hemen yansımıyorsa şunu deneyin: npx expo customize tsconfig.json ile tsconfig'inize .expo/types/**/*.ts'in dahil edildiğini doğrulayın ve dev server'ı yeniden başlatın. Genelde bu çözüyor.

Tab Bar İstenmeyen Yerlerde Görünüyor

(tabs) grubunun dışındaki bir ekrana navigasyon yapıldığında tab bar gizlenmeli. Eğer öyle olmuyorsa layout hiyerarşisini gözden geçirin: tab içeren rotaları (tabs)/ grubunda, detay ekranlarını ise üst seviyede tutmak gerekiyor.

Modal Kapanmıyor

Modal'dan çıkış için router.dismiss() ya da router.back() kullanın. router.replace("/") stack'i sıfırlar — modal akışlarda bu büyük olasılıkla istemediğiniz davranış.

Üretim Kontrol Listesi

  • typedRoutes etkin ve CI'da tsc --noEmit çalışıyor
  • ✅ Her dinamik rota için +not-found.tsx tanımlı
  • ✅ Auth korumalı rotalar Stack.Protected ya da Redirect ile sarmalı
  • ✅ Universal links / app links app.json içinde tanımlı
  • ✅ Web hedefi için web.output stratejisi (static / server) seçili
  • ✅ Deep link davranışı gerçek cihazda test edildi (simulator yetmez, lütfen)
  • ✅ EAS Update kanalları eas.json içinde yapılandırılmış

Sıkça Sorulan Sorular (SSS)

Expo Router, React Navigation'ın yerini mi alıyor?

Hayır — Expo Router, React Navigation'ın üzerinde çalışan bir katman. Altta hâlâ React Navigation API'leri kullanılıyor; ihtiyaç duyduğunuzda useNavigation ile doğrudan erişebilirsiniz. Expo Router yalnızca konfigürasyonu dosya sistemine devrediyor.

Expo Router için Expo SDK kullanmak zorunda mıyım?

Maalesef evet. Expo Router, Expo CLI'nin Metro yapılandırmasına ve expo-linking gibi paketlere bağımlı. Bare React Native projelerinde kullanmak için projenizi Expo'ya taşımanız (continuous native generation ile mümkün) gerekiyor. İyi haber: SDK 50+ tüm bu adımı tek komuta indirdi.

Typed routes neden bazı dosyalarda çalışmıyor?

Tipler yalnızca app/ içindeki .tsx dosyalarından üretiliyor. Eğer dosya app/ dışında ya da .js uzantılı ise tip oluşmaz. Ek olarak, experiments.typedRoutes: true ayarını yaptıktan sonra dev server'ı en az bir kez yeniden başlatmanız gerekiyor — bu adımı atlamak çok yaygın bir hata.

Bir rotaya parametre geçirmenin en güvenli yolu nedir?

Tip güvenli geçiş için her zaman nesne formunu tercih edin: router.push({ pathname: "/product/[id]", params: { id: "42" } }). URL'ye gömülü string birleştirme (router.push("/product/" + id)) tipo riski taşır ve özel karakterleri kaçırmaz. Yani üretimde patlamak istemiyorsanız, nesne formuna sadık kalın.

Expo Router web tarafında SEO sağlar mı?

Evet. SDK 52'den itibaren statik render (web.output: "static") ile her rota için ayrı HTML üretiliyor; bu sayfalar tam meta etiketleri ve doldurulmuş içerikle Google'a sunuluyor. Server components ile dinamik içerik için server-side rendering de mümkün.

EAS Update ile Expo Router rota değişiklikleri OTA gönderilebilir mi?

Evet, gönderilebilir. Yeni bir rota dosyası eklemek, kaldırmak ya da içeriğini değiştirmek tamamen JavaScript düzeyinde olduğundan EAS Update ile dakikalar içinde tüm kullanıcılara ulaştırılabilir. Ancak yeni bir native bağımlılık eklediyseniz (örneğin yeni bir navigation paketi), runtime version'ı artırıp yeni bir build çıkarmanız gerekir — bunu unutmayın.

Sonuç

Expo Router v4, React Native'de navigasyonun karmaşıklığını dosya sisteminin sezgiselliğine indirgeyen olgun bir çözüm. Dosya tabanlı yapısı, otomatik deep linking'i, tip güvenli rotaları ve birinci sınıf web desteği ile 2026'da yeni başlayan her React Native projesinin varsayılan tercihi olmayı hak ediyor — en azından bana sorarsanız. Mevcut React Navigation projeleriniz varsa, geçişi aşamalı yapabilir ve kazanımlardan hemen faydalanmaya başlayabilirsiniz. Hayırlı kodlar.

Yazar Hakkında Editorial Team

Our team of expert writers and editors.