Expo Router v7: Komplett guide till filbaserad navigering i React Native

Lär dig allt om Expo Router v7 — filbaserad routing, tabs, autentisering, typed routes och API routes. Praktisk guide med kodexempel för React Native med SDK 55.

Varför Expo Router har blivit standarden för React Native-navigering

Okej, låt oss snacka om Expo Router v7. Med lanseringen av Expo SDK 55 i februari 2026 fick vi den mest mogna versionen hittills av det filbaserade routingsystemet för React Native — och ärligt talat, det är ganska imponerande hur långt det har kommit.

Navigering i React Native brukade vara… ja, krångligt. Manuell konfiguration av navigatorer, djuplänkningsscheman och skärmregistreringar. Expo Router ersätter allt det med en konventionsbaserad metod där din mappstruktur är dina routes. Simpelt och elegant.

Har du jobbat med Next.js eller Remix? Då känner du igen konceptet direkt. Varje fil i app/-katalogen blir automatiskt en navigerbar skärm — på Android, iOS och webben. I den här guiden går vi igenom allt från grunderna i filbaserad routing till mer avancerade mönster som autentiseringsflöden, typed routes och API-routes.

Komma igång med Expo Router v7

Nya Expo-projekt skapade med SDK 55 inkluderar Expo Router som standard, så du behöver inte konfigurera något extra. Skapa ett nytt projekt så här:

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

Har du redan ett befintligt projekt? Installera de nödvändiga paketen:

npx expo install expo-router expo-linking expo-constants expo-status-bar

Kontrollera sedan att din app.json har rätt schema-konfiguration. Det här steget är lätt att missa, men det är viktigt för att djuplänkning ska fungera korrekt:

{
  "expo": {
    "scheme": "mitt-projekt",
    "web": {
      "bundler": "metro",
      "output": "server"
    },
    "plugins": ["expo-router"]
  }
}

Grunderna i filbaserad routing

Hjärtat i Expo Router är principen att filsystemet definierar dina routes. Varje fil i app/-katalogen med en default export blir automatiskt en tillgänglig skärm. Inga navigatorkonfigurationer, inga separata routefiler.

Mappstruktur och URL-mappning

Här är ett typiskt exempel — och det säger egentligen allt om hur systemet fungerar:

app/
  _layout.tsx          → Rotlayout (wraps allt)
  index.tsx            → /
  about.tsx            → /about
  settings/
    _layout.tsx        → Layout för settings-gruppen
    index.tsx          → /settings
    profile.tsx        → /settings/profile
    notifications.tsx  → /settings/notifications

Filer med namnet index.tsx matchar sin överordnade katalog utan att lägga till ett extra URL-segment. Så app/index.tsx matchar / och app/settings/index.tsx matchar /settings. Ganska intuitivt, eller hur?

Layoutfiler med _layout.tsx

Layoutfiler omsluter sina underliggande routes med en navigator. De definierar delad UI som headers, tabbars och sidomenyer.

Rotlayouten i app/_layout.tsx är obligatorisk och bestämmer appens övergripande navigationsstruktur:

import { Stack } from 'expo-router';

export default function RootLayout() {
  return (
    <Stack>
      <Stack.Screen name="index" options={{ title: 'Hem' }} />
      <Stack.Screen name="about" options={{ title: 'Om oss' }} />
      <Stack.Screen name="settings" options={{ headerShown: false }} />
    </Stack>
  );
}

Navigering mellan skärmar

Expo Router erbjuder flera sätt att navigera, och det är här det börjar bli riktigt smidigt:

import { Link, useRouter } from 'expo-router';
import { View, Text, Pressable } from 'react-native';

export default function HomeScreen() {
  const router = useRouter();

  return (
    <View>
      {/* Deklarativ navigering med Link */}
      <Link href="/about">Gå till Om oss</Link>

      {/* Programmatisk navigering */}
      <Pressable onPress={() => router.push('/settings/profile')}>
        <Text>Öppna profil</Text>
      </Pressable>

      {/* Ersätt nuvarande skärm (ingen bakåtknapp) */}
      <Pressable onPress={() => router.replace('/settings')}>
        <Text>Ersätt med inställningar</Text>
      </Pressable>
    </View>
  );
}

router.navigate() pushar en ny skärm eller rullar tillbaka till en befintlig route. Använd router.push() för att alltid lägga till en ny skärm på stacken, och router.back() för att gå tillbaka. Min personliga rekommendation: håll dig till push() som standard — det är mer förutsägbart.

Dynamiska routes

Dynamiska routes gör att du kan skapa skärmar som accepterar parametrar direkt i filnamnet. Hakparenteser definierar dynamiska segment:

app/
  user/
    [id].tsx           → /user/123, /user/abc
  blog/
    [...slug].tsx      → /blog/2026/mars/min-artikel (catch-all)

Inuti den dynamiska skärmen hämtar du parametern med useLocalSearchParams:

import { useLocalSearchParams } from 'expo-router';
import { Text, View } from 'react-native';

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

  return (
    <View>
      <Text>Användare: {id}</Text>
    </View>
  );
}

Catch-all-routes med [...slug] fångar alla undersegment och returnerar dem som en array. Perfekt för bloggar och CMS-baserat innehåll.

Navigeringsmönster: Tabs, Stack och Drawer

Tabbnavigering

Tabbar är det absolut vanligaste navigeringsmönstret i mobilappar. Om du bygger en app har du förmodligen tabs — det är bara så det är. Skapa en tabbayout med Tabs-komponenten:

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

export default function TabsLayout() {
  return (
    <Tabs screenOptions={{ tabBarActiveTintColor: '#007AFF' }}>
      <Tabs.Screen
        name="index"
        options={{
          title: 'Hem',
          tabBarIcon: ({ color, size }) => (
            <Ionicons name="home" color={color} size={size} />
          ),
        }}
      />
      <Tabs.Screen
        name="search"
        options={{
          title: 'Sök',
          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>
  );
}

I SDK 55 hanterar tabblayouter automatiskt safe area-insets på båda plattformarna, vilket sparar en hel del huvudvärk. Egenskapen resetOnFocus (som tidigare hette bara reset) styr om tabbens stack ska återställas vid byte.

Drawer-navigering

För sidomenyer behöver du installera drawer-paketet separat:

npx expo install @react-navigation/drawer react-native-gesture-handler react-native-reanimated

Sedan skapar du en drawer-layout — det är rakt på sak:

// app/(drawer)/_layout.tsx
import { Drawer } from 'expo-router/drawer';

export default function DrawerLayout() {
  return (
    <Drawer>
      <Drawer.Screen name="index" options={{ title: 'Hem' }} />
      <Drawer.Screen name="settings" options={{ title: 'Inställningar' }} />
    </Drawer>
  );
}

Nästlad navigering: Tabs inuti Stack

Ett riktigt vanligt mönster (och något jag använder i nästan alla mina projekt) är att nästla tabbar inuti en stack-navigator. Grupper med parenteser () organiserar routes utan att påverka URL-strukturen:

app/
  _layout.tsx              → Stack (rot)
  (tabs)/
    _layout.tsx            → Tabs
    index.tsx              → / (Hem-tabb)
    search.tsx             → /search
    profile.tsx            → /profile
  modal.tsx                → /modal (presenteras som modal)

I rotlayouten konfigurerar du modalen så här:

import { Stack } from 'expo-router';

export default function RootLayout() {
  return (
    <Stack>
      <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
      <Stack.Screen name="modal" options={{ presentation: 'modal' }} />
    </Stack>
  );
}

Autentisering med Expo Router

Autentisering är ofta den delen som folk kämpar mest med — och det är förståeligt. Expo Router hanterar det genom redirect-mönster snarare än villkorlig rendering av navigatorer. Det rekommenderade mönstret använder en SessionProvider och hooken useSession.

Steg 1: Skapa AuthContext

// ctx.tsx
import { use, createContext, type PropsWithChildren } from 'react';
import { useStorageState } from './useStorageState';

const AuthContext = createContext<{
  signIn: (token: string) => void;
  signOut: () => void;
  session?: string | null;
  isLoading: boolean;
}>({
  signIn: () => null,
  signOut: () => null,
  session: null,
  isLoading: false,
});

export function useSession() {
  const value = use(AuthContext);
  if (!value) {
    throw new Error('useSession måste användas inom en SessionProvider');
  }
  return value;
}

export function SessionProvider({ children }: PropsWithChildren) {
  const [[isLoading, session], setSession] = useStorageState('session');

  return (
    <AuthContext
      value={{
        signIn: (token: string) => setSession(token),
        signOut: () => setSession(null),
        session,
        isLoading,
      }}>
      {children}
    </AuthContext>
  );
}

Steg 2: Skydda routes i layouten

Strukturera dina routes i grupper — en för autentisering och en för den skyddade appen:

app/
  _layout.tsx           → Wraps SessionProvider
  sign-in.tsx           → Inloggningsskärm
  (app)/
    _layout.tsx         → Kontrollerar session, redirect om ej inloggad
    (tabs)/
      _layout.tsx       → Tabbar
      index.tsx
      profile.tsx
// app/(app)/_layout.tsx
import { Redirect, Stack } from 'expo-router';
import { Text } from 'react-native';
import { useSession } from '../../ctx';

export default function AppLayout() {
  const { session, isLoading } = useSession();

  if (isLoading) {
    return <Text>Laddar...</Text>;
  }

  if (!session) {
    return <Redirect href="/sign-in" />;
  }

  return <Stack />;
}

Det fina med det här mönstret är att oinloggade användare alltid omdirigeras — även vid djuplänkning. Tokens lagras säkert med expo-secure-store på native och localStorage på webben.

Protected Routes i SDK 55

Från SDK 53 och framåt stöder Expo Router även en Protected-wrapper för mer finkornig kontroll. Du kan exempelvis dölja en VIP-tabb för användare som inte uppfyller villkoret:

<Tabs>
  <Tabs.Screen name="home" />
  <Tabs.Protected guard={isVipMember}>
    <Tabs.Screen name="vip" options={{ title: 'VIP' }} />
  </Tabs.Protected>
</Tabs>

Ganska smidigt, eller hur?

Typed Routes för typsäker navigering

Det här är ärligt talat en av mina favoritfunktioner i Expo Router. Automatiskt genererade TypeScript-typer som fångar trasiga länkar vid kompilering? Ja tack. Aktivera det i app.json:

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

När du startar dev-servern genererar Expo CLI automatiskt typfiler baserade på din mappstruktur. Det innebär att Link-komponenten och router.push() bara accepterar giltiga routes:

// ✅ Kompilerar — /settings/profile finns
router.push('/settings/profile');

// ❌ TypeScript-fel — /settings/nonexistent finns inte
router.push('/settings/nonexistent');

Detta minskar navigeringsrelaterade buggar avsevärt. De genererade typfilerna exkluderas automatiskt från Git, så de stör inte i ditt repo.

API Routes: Serverkod i din app

Expo Router stöder API Routes som körs på servern — och det öppnar upp en hel del möjligheter. Skapa en fil med tillägget +api.ts i app/-katalogen:

// app/api/users+api.ts
export async function GET(request: Request) {
  const users = await db.query('SELECT * FROM users');
  return Response.json(users);
}

export async function POST(request: Request) {
  const body = await request.json();
  const user = await db.insert('users', body);
  return Response.json(user, { status: 201 });
}

API Routes körs i en sandlådad servermiljö och har tillgång till alla miljövariabler — inte bara de med EXPO_PUBLIC_-prefix. Det gör dem perfekta för API-nycklar, autentiseringstokens och annan känslig serverlogik.

Värt att notera: i SDK 55 har de gamla typerna ExpoRequest och ExpoResponse tagits bort till förmån för webbstandarden Request/Response. En välkommen förändring som gör koden mer portabel.

Djuplänkning och universella länkar

En av de absolut största fördelarna med Expo Router är att djuplänkning fungerar automatiskt. Varje skärm i din app kan nås via en URL utan extra konfiguration.

Det innebär att:

  • Delningslänkar fungerar direkt — mitt-projekt://user/42 öppnar rätt skärm
  • Webbadresser fungerar identiskt på alla plattformar
  • Oinloggade användare som öppnar en djuplänk omdirigeras till inloggning och sedan tillbaka till den ursprungliga skärmen

För universella länkar (alltså HTTPS-baserade) konfigurerar du intentFilters i app.json och verifierar din domän med Apple och Google. Det kräver lite setup, men resultatet är värt det.

Nyheter i Expo Router v7 (SDK 55)

Den senaste versionen innehåller flera riktigt bra uppgraderingar. Här är de viktigaste:

  • Synkrona layouter: Skärmar uppdateras synkront som standard, vilket eliminerar de där irriterande visuella flicksen vid navigering
  • Colors API: Automatisk Material 3-styling på Android och systemfärger på iOS — snygg standarddesign utan extra arbete
  • Apple Zoom Transition: Delade elementövergångar med native-kvalitet, aktiverade som standard på iOS
  • Stack.Toolbar (iOS): Native UIToolbar för åtgärdsknappar längst ned på skärmen
  • SplitView (experimentellt): Multiplanel-gränssnitt för surfplattor och skrivbord — äntligen!
  • Automatiska safe area-insets: Tabblayouter hanterar insets automatiskt på båda plattformarna
  • resetOnFocus: Den tidigare reset-propen har bytt namn till resetOnFocus för bättre tydlighet

Bästa praxis för Expo Router 2026

Här är mina rekommendationer efter att ha jobbat med Expo Router i produktion:

  1. Använd grupper för logisk organisation. Parenteser som (auth) och (app) organiserar routes utan att påverka URL-strukturen. Det gör projektet mycket lättare att navigera.
  2. Undvik att returnera null från rotkomponenter. Rendera istället en laddningsindikator medan teckensnitt och resurser laddas — annars får du en tom skärm som blinkar förbi.
  3. Skicka bara serialiserbara parametrar. Query-parametrar stöder bara string, number och boolean på toppnivå — inte objekt eller funktioner. Det här biter folk ganska ofta.
  4. Aktivera Async Routes i stora projekt. Bundle splitting isolerar fel till enskilda routes och förbättrar utvecklingshastigheten avsevärt.
  5. Använd router.push() som standard. Undvik router.navigate() om du inte specifikt vill att stacken ska lindas tillbaka till en befintlig route.
  6. Aktivera typed routes tidigt. Ju tidigare du aktiverar det, desto fler navigeringsbuggar fångas vid kompilering istället för i produktion.

Vanliga frågor

Kan jag använda React Navigation tillsammans med Expo Router?

Absolut. Expo Router är byggt ovanpå React Navigation, så du har tillgång till alla underliggande API:er som navigation.openDrawer() och navigation.setOptions(). Du kan fritt blanda den filbaserade approachen med React Navigations imperativa API:er vid behov.

Hur migrerar jag från React Navigation till Expo Router?

Expo har en officiell migreringsguide. Grundstegen: konvertera dina navigatorer till _layout.tsx-filer, flytta skärmarna till filer i app/-katalogen och ersätt navigation.navigate()-anrop med router.push() eller Link-komponenter. Det bästa? Du kan göra det inkrementellt — hela appen behöver inte migreras på en gång.

Fungerar Expo Router med React Native utan Expo?

Expo Router kräver Expo-ramverket. Om du har ett rent React Native CLI-projekt behöver du först installera Expo med npx install-expo-modules. Meta rekommenderar officiellt Expo för React Native-utveckling sedan 2025, så integrationen är smidigare än du kanske tror.

Hur hanterar Expo Router modaler och bottom sheets?

Modaler konfigureras genom att sätta presentation: 'modal' i skärmens options inom layoutfilen. Skapa en dedikerad modal-fil (t.ex. app/modal.tsx) och konfigurera den i rotlayoutens Stack. För bottom sheets rekommenderar jag @gorhom/bottom-sheet — det fungerar riktigt bra tillsammans med Expo Routers navigering.

Stöder Expo Router server-side rendering och React Server Components?

Expo Router stöder statisk rendering för webben och API Routes för serverkod. React Server Components (RSC) på mobilen är under aktiv utveckling och finns som experimentell funktion. Det som byggs nu kommer att möjliggöra att datatung logik flyttas till servern, vilket borde minska paketstorlekarna avsevärt i framtida versioner. Spännande tider.

Om Författaren Editorial Team

Our team of expert writers and editors.