למה אימות ב-Expo Router השתנה לגמרי ב-2026
אם אתם מפתחי React Native, כנראה כבר שמעתם על השינוי הגדול — Expo SDK 53 שינה את כללי המשחק באימות משתמשים וניהול Routes מוגנים. אני חייב להודות, עד עכשיו ניהול אימות באפליקציות React Native היה כאב ראש אמיתי: redirects ידניים, בדיקות מפוזרות בכל קובץ layout, ובאגים מעצבנים עם deep links שעוקפים את ההגנות שלכם.
היום? יש דרך הרבה יותר טובה.
עם הכנסת Stack.Protected ו-Tabs.Protected ב-Expo Router, אתם יכולים להגדיר בצורה דקלרטיבית אילו מסכים דורשים אימות ואילו פתוחים לכולם — הכל בקובץ layout אחד, בלי לפזר לוגיקה בכל מקום. וזה לא הכל: SDK 53 מפעיל את הארכיטקטורה החדשה (Fabric ו-TurboModules) כברירת מחדל, מה שמשפר דרמטית את הביצועים של כל מנגנון האימות.
במדריך הזה נבנה מערכת אימות מלאה צעד אחר צעד — מהגדרת הפרויקט, דרך Context לניהול session, אחסון מאובטח של טוקנים, הגנה על routes עם Stack.Protected, ועד בקרת גישה מבוססת תפקידים (RBAC). כולל דוגמאות קוד שעובדות בפרודקשן.
הגדרת פרויקט Expo Router עם SDK 53
אז בואו נתחיל מההתחלה. נגדיר פרויקט חדש עם כל הספריות שצריך. שימו לב — אנחנו עובדים עם SDK 53 שכולל React Native 0.79 ו-React 19.
# יצירת פרויקט חדש
npx create-expo-app@latest my-auth-app
cd my-auth-app
# התקנת ספריות נדרשות
npx expo install expo-router expo-secure-store expo-linking expo-constants expo-status-bar react-native-safe-area-context react-native-screens
עדכנו את קובץ package.json כדי להגדיר את נקודת הכניסה של Expo Router:
{
"main": "expo-router/entry"
}
ודאו שקובץ app.json מוגדר ככה:
{
"expo": {
"scheme": "my-auth-app",
"plugins": ["expo-router", "expo-secure-store"],
"newArchEnabled": true
}
}
טיפ קטן — newArchEnabled: true הוא כבר ברירת המחדל ב-SDK 53, אבל אני ממליץ לכתוב את זה מפורשות כדי שהכוונה תהיה ברורה לכל מי שקורא את הקוד.
מבנה התיקיות המומלץ
מבנה התיקיות הוא קריטי ב-Expo Router כי הוא מגדיר את ה-routes באופן אוטומטי. הנה המבנה שנעבוד איתו:
app/
├── _layout.tsx # Layout ראשי + הגדרת Protected Routes
├── index.tsx # מסך ברירת מחדל / הפניה
├── (auth)/
│ ├── _layout.tsx # Layout עבור מסכי אימות
│ ├── sign-in.tsx # מסך התחברות
│ └── sign-up.tsx # מסך הרשמה
├── (app)/
│ ├── _layout.tsx # Layout עבור מסכי האפליקציה
│ ├── index.tsx # מסך בית
│ ├── profile.tsx # פרופיל משתמש
│ └── settings.tsx # הגדרות
├── (admin)/
│ ├── _layout.tsx # Layout עבור ניהול
│ └── dashboard.tsx # לוח בקרה למנהלים
ctx/
├── AuthContext.tsx # Context לניהול אימות
├── useStorageState.ts # Hook לאחסון מאובטח
בניית Context לניהול אימות
הלב של מערכת האימות שלנו הוא React Context שמנהל את מצב ה-session ומספק פונקציות התחברות והתנתקות לכל הקומפוננטות. נתחיל בבניית hook מותאם אישית לאחסון מאובטח.
Hook לאחסון מאובטח עם expo-secure-store
נקודה חשובה לפני שמתחילים — לעולם אל תשמרו טוקנים ב-AsyncStorage. רציני, אל תעשו את זה. הנתונים שם לא מוצפנים וכל מי שיש לו גישה למכשיר יכול לקרוא אותם. במקום זה, נשתמש ב-expo-secure-store שמצפין נתונים באמצעות Keychain ב-iOS ו-Keystore ב-Android.
// ctx/useStorageState.ts
import * as SecureStore from 'expo-secure-store';
import { useCallback, useEffect, useReducer } from 'react';
import { Platform } from 'react-native';
type UseStateHook<T> = [[boolean, T | null], (value: T | null) => void];
function useAsyncState<T>(
initialValue: [boolean, T | null] = [true, null]
): UseStateHook<T> {
return useReducer(
(state: [boolean, T | null], action: T | null = null): [boolean, T | null] => [false, action],
initialValue
) as UseStateHook<T>;
}
export async function setStorageItemAsync(key: string, value: string | null) {
if (Platform.OS === 'web') {
try {
if (value === null) {
localStorage.removeItem(key);
} else {
localStorage.setItem(key, value);
}
} catch (e) {
console.error('Local storage is unavailable:', e);
}
} else {
if (value === null) {
await SecureStore.deleteItemAsync(key);
} else {
await SecureStore.setItemAsync(key, value);
}
}
}
export function useStorageState(key: string): UseStateHook<string> {
const [state, setState] = useAsyncState<string>();
useEffect(() => {
if (Platform.OS === 'web') {
try {
const value = localStorage.getItem(key);
setState(value);
} catch (e) {
console.error('Local storage is unavailable:', e);
}
} else {
SecureStore.getItemAsync(key).then((value) => {
setState(value);
});
}
}, [key]);
const setValue = useCallback(
(value: string | null) => {
setState(value);
setStorageItemAsync(key, value);
},
[key]
);
return [state, setValue];
}
AuthContext מלא
עכשיו נבנה את ה-Context עצמו. זה החלק שמנהל את כל מצב האימות באפליקציה:
// ctx/AuthContext.tsx
import React, { createContext, useContext, type PropsWithChildren } from 'react';
import { useStorageState } from './useStorageState';
interface AuthSession {
token: string;
role: 'user' | 'admin';
}
interface AuthContextType {
signIn: (email: string, password: string) => Promise<void>;
signUp: (email: string, password: string) => Promise<void>;
signOut: () => void;
session: AuthSession | null;
isLoading: boolean;
}
const AuthContext = createContext<AuthContextType>({
signIn: async () => {},
signUp: async () => {},
signOut: () => {},
session: null,
isLoading: false,
});
export function useSession() {
const value = useContext(AuthContext);
if (process.env.NODE_ENV !== 'production') {
if (!value) {
throw new Error('useSession must be wrapped in <SessionProvider />');
}
}
return value;
}
export function SessionProvider({ children }: PropsWithChildren) {
const [[isLoading, storedSession], setStoredSession] =
useStorageState('session');
const session: AuthSession | null = storedSession
? JSON.parse(storedSession)
: null;
return (
<AuthContext.Provider
value={{
signIn: async (email: string, password: string) => {
// בפרודקשן — קריאת API אמיתית לשרת
const response = await fetch('https://api.example.com/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
if (!response.ok) {
throw new Error('Authentication failed');
}
const data = await response.json();
setStoredSession(
JSON.stringify({ token: data.token, role: data.role })
);
},
signUp: async (email: string, password: string) => {
const response = await fetch('https://api.example.com/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
if (!response.ok) {
throw new Error('Registration failed');
}
const data = await response.json();
setStoredSession(
JSON.stringify({ token: data.token, role: data.role })
);
},
signOut: () => {
setStoredSession(null);
},
session,
isLoading,
}}
>
{children}
</AuthContext.Provider>
);
}
שימו לב שאנחנו שומרים גם את ה-token וגם את ה-role של המשתמש. זה נראה כמו פרט קטן, אבל זה יהיה קריטי כשנגיע לבקרת גישה מבוססת תפקידים בהמשך.
Stack.Protected — הדרך החדשה להגן על Routes
אוקיי, עכשיו מגיע החלק הכי מעניין — הגדרת ה-routes המוגנים באמצעות Stack.Protected. בכנות? זה הפיצ'ר שגרם לי הכי להתלהב ב-SDK 53.
מה היה קודם (ולמה זה היה בעייתי)
לפני SDK 53, השיטה המקובלת הייתה שימוש ב-redirects ידניים בתוך קבצי layout:
// הגישה הישנה — SDK 52 ומטה (לא מומלצת יותר)
import { Redirect } from 'expo-router';
export default function AppLayout() {
const { session } = useSession();
// בדיקה ידנית בכל layout — שגיאות, כפילויות, באגים
if (!session) {
return <Redirect href="/sign-in" />;
}
return <Stack />;
}
הבעיות עם הגישה הזו? רבות ומגוונות. הלוגיקה הייתה מפוזרת בקבצי layout שונים, deep links יכלו לעקוף את ההגנות, והייתה הבזקה מעצבנה של מסכים מוגנים לפני ה-redirect. כל מי שעבד עם זה מכיר את התסכול.
הגישה החדשה עם Stack.Protected
הנה ה-layout הראשי שמנהל את כל ההגנות במקום אחד:
// app/_layout.tsx
import { Stack } from 'expo-router';
import { SessionProvider, useSession } from '../ctx/AuthContext';
import { ActivityIndicator, View } from 'react-native';
function RootNavigator() {
const { session, isLoading } = useSession();
if (isLoading) {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<ActivityIndicator size="large" />
</View>
);
}
return (
<Stack screenOptions={{ headerShown: false }}>
{/* מסכי אימות — נגישים רק כשאין session */}
<Stack.Protected guard={!session}>
<Stack.Screen name="(auth)" />
</Stack.Protected>
{/* מסכי אפליקציה — דורשים session פעיל */}
<Stack.Protected guard={!!session}>
<Stack.Screen name="(app)" />
</Stack.Protected>
{/* מסכי ניהול — דורשים session + תפקיד admin */}
<Stack.Protected guard={session?.role === 'admin'}>
<Stack.Screen name="(admin)" />
</Stack.Protected>
</Stack>
);
}
export default function RootLayout() {
return (
<SessionProvider>
<RootNavigator />
</SessionProvider>
);
}
וזהו. באמת, זה הכל. כל ההגנות מוגדרות במקום אחד, בצורה דקלרטיבית וברורה. אם משתמש מנסה לנווט ל-route מוגן — הוא פשוט לא יוכל. אין redirect, אין הבזקה, אין באגים.
איך זה עובד מאחורי הקלעים
כש-guard מוגדר ל-false, ה-routes שבתוך אותו Stack.Protected פשוט לא קיימים מבחינת הניווט. כלומר, זה לא שהם מוסתרים — הם ממש לא שם.
אם המשתמש כבר נמצא על מסך מוגן וה-guard משתנה ל-false (למשל אחרי התנתקות), הוא מופנה אוטומטית למסך העוגן (anchor) — בדרך כלל מסך ה-index, או המסך הראשון הזמין ב-stack. כל ההיסטוריה של אותם routes נמחקת מה-navigation history.
מימוש מסכי האימות
בואו נבנה את מסכי ההתחברות וההרשמה. אני אראה כאן את מסך ההתחברות המלא עם טיפול בשגיאות ו-loading state:
// app/(auth)/_layout.tsx
import { Stack } from 'expo-router';
export default function AuthLayout() {
return (
<Stack>
<Stack.Screen
name="sign-in"
options={{ title: 'התחברות' }}
/>
<Stack.Screen
name="sign-up"
options={{ title: 'הרשמה' }}
/>
</Stack>
);
}
// app/(auth)/sign-in.tsx
import { useState } from 'react';
import {
View, Text, TextInput, Pressable,
StyleSheet, Alert, ActivityIndicator
} from 'react-native';
import { Link } from 'expo-router';
import { useSession } from '../../ctx/AuthContext';
export default function SignIn() {
const { signIn } = useSession();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const handleSignIn = async () => {
if (!email || !password) {
Alert.alert('שגיאה', 'נא למלא את כל השדות');
return;
}
setLoading(true);
try {
await signIn(email, password);
// הניווט יתבצע אוטומטית —
// Stack.Protected יזהה את ה-session החדש
} catch (error) {
Alert.alert('שגיאת התחברות', 'אימייל או סיסמה שגויים');
} finally {
setLoading(false);
}
};
return (
<View style={styles.container}>
<Text style={styles.title}>ברוכים הבאים</Text>
<TextInput
style={styles.input}
placeholder="אימייל"
value={email}
onChangeText={setEmail}
keyboardType="email-address"
autoCapitalize="none"
/>
<TextInput
style={styles.input}
placeholder="סיסמה"
value={password}
onChangeText={setPassword}
secureTextEntry
/>
<Pressable
style={styles.button}
onPress={handleSignIn}
disabled={loading}
>
{loading ? (
<ActivityIndicator color="#fff" />
) : (
<Text style={styles.buttonText}>התחברות</Text>
)}
</Pressable>
<Link href="/(auth)/sign-up" style={styles.link}>
אין לכם חשבון? הירשמו כאן
</Link>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 24,
justifyContent: 'center',
backgroundColor: '#f5f5f5',
},
title: {
fontSize: 28,
fontWeight: 'bold',
textAlign: 'center',
marginBottom: 32,
},
input: {
backgroundColor: '#fff',
borderRadius: 12,
padding: 16,
marginBottom: 16,
fontSize: 16,
borderWidth: 1,
borderColor: '#ddd',
},
button: {
backgroundColor: '#007AFF',
borderRadius: 12,
padding: 16,
alignItems: 'center',
marginTop: 8,
},
buttonText: {
color: '#fff',
fontSize: 18,
fontWeight: '600',
},
link: {
textAlign: 'center',
marginTop: 16,
color: '#007AFF',
fontSize: 16,
},
});
שימו לב לדבר הקסום כאן — אחרי התחברות מוצלחת, אתם לא צריכים לנווט לשום מקום. בלי router.push, בלי Redirect. ה-Stack.Protected מזהה אוטומטית שה-session השתנה ומציג את ה-routes המתאימים. זה פשוט עובד, ולדעתי זו אחת התוספות הכי אלגנטיות ב-Expo Router.
בקרת גישה מבוססת תפקידים (RBAC)
אחד היתרונות הגדולים של Stack.Protected הוא היכולת ליצור שכבות הגנה מקוננות. כבר ראינו ב-layout הראשי שהמסכים של (admin) מוגנים בתנאי session?.role === 'admin'. בואו נרחיב את זה.
// app/(admin)/_layout.tsx
import { Stack } from 'expo-router';
import { useSession } from '../../ctx/AuthContext';
export default function AdminLayout() {
const { session } = useSession();
return (
<Stack>
<Stack.Screen
name="dashboard"
options={{ title: 'לוח בקרה' }}
/>
{/* ניתן להוסיף שכבת RBAC נוספת גם בתוך ה-admin */}
<Stack.Protected guard={session?.role === 'admin'}>
<Stack.Screen
name="users-management"
options={{ title: 'ניהול משתמשים' }}
/>
</Stack.Protected>
</Stack>
);
}
// app/(admin)/dashboard.tsx
import { View, Text, StyleSheet, Pressable } from 'react-native';
import { useSession } from '../../ctx/AuthContext';
export default function AdminDashboard() {
const { session, signOut } = useSession();
return (
<View style={styles.container}>
<Text style={styles.title}>לוח בקרה — ניהול</Text>
<Text style={styles.info}>תפקיד: {session?.role}</Text>
<View style={styles.statsGrid}>
<View style={styles.statCard}>
<Text style={styles.statNumber}>1,234</Text>
<Text style={styles.statLabel}>משתמשים פעילים</Text>
</View>
<View style={styles.statCard}>
<Text style={styles.statNumber}>56</Text>
<Text style={styles.statLabel}>משתמשים חדשים היום</Text>
</View>
</View>
<Pressable style={styles.logoutButton} onPress={signOut}>
<Text style={styles.logoutText}>התנתקות</Text>
</Pressable>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 24, backgroundColor: '#f0f0f0' },
title: { fontSize: 24, fontWeight: 'bold', marginBottom: 8 },
info: { fontSize: 16, color: '#666', marginBottom: 24 },
statsGrid: { flexDirection: 'row', gap: 16, marginBottom: 24 },
statCard: {
flex: 1, backgroundColor: '#fff', padding: 20,
borderRadius: 12, alignItems: 'center',
},
statNumber: { fontSize: 28, fontWeight: 'bold', color: '#007AFF' },
statLabel: { fontSize: 14, color: '#666', marginTop: 4 },
logoutButton: {
backgroundColor: '#FF3B30', borderRadius: 12,
padding: 16, alignItems: 'center',
},
logoutText: { color: '#fff', fontSize: 18, fontWeight: '600' },
});
היופי של הגישה הזו הוא שאפשר ליצור היררכיה שלמה של הרשאות. משתמש רגיל רואה את (app), מנהל רואה גם את (admin), וסופר-אדמין יכול לראות מסכים נוספים בתוך ה-admin. הכל דקלרטיבי ונקי — בדיוק כמו שצריך להיות.
שילוב Tabs.Protected עם Tab Navigation
רוב האפליקציות משתמשות ב-Tab Navigation, אז בואו נראה איך Tabs.Protected עובד בפועל:
// app/(app)/_layout.tsx
import { Tabs } from 'expo-router';
import { useSession } from '../../ctx/AuthContext';
import { Ionicons } from '@expo/vector-icons';
export default function AppLayout() {
const { session } = useSession();
return (
<Tabs
screenOptions={{
tabBarActiveTintColor: '#007AFF',
headerShown: true,
}}
>
<Tabs.Screen
name="index"
options={{
title: 'בית',
tabBarIcon: ({ color, size }) => (
<Ionicons name="home" size={size} color={color} />
),
}}
/>
<Tabs.Screen
name="profile"
options={{
title: 'פרופיל',
tabBarIcon: ({ color, size }) => (
<Ionicons name="person" size={size} color={color} />
),
}}
/>
{/* טאב הגדרות — רק למשתמשים עם הרשאה */}
<Tabs.Protected guard={session?.role === 'admin'}>
<Tabs.Screen
name="settings"
options={{
title: 'הגדרות',
tabBarIcon: ({ color, size }) => (
<Ionicons name="settings" size={size} color={color} />
),
}}
/>
</Tabs.Protected>
</Tabs>
);
}
כש-guard הוא false, הטאב פשוט נעלם. לא צריך תנאי if, לא צריך לסתיר אלמנטים ידנית. אני זוכר את הימים שהיינו צריכים לכתוב לוגיקה מותאמת אישית ל-tabBarButton כדי להסתיר טאבים — עכשיו זה שורה אחת.
טיפול ב-Deep Linking עם אימות
אחד היתרונות המשמעותיים של Stack.Protected הוא הטיפול האוטומטי ב-deep links. ב-Expo Router, כל route מקבל deep link אוטומטי. אבל מה קורה כשמשתמש לא מחובר לוחץ על deep link ל-route מוגן?
עם Stack.Protected, התשובה פשוטה — אם ה-route מוגן וה-guard הוא false, המשתמש מופנה למסך העוגן. אין צורך בלוגיקה מיוחדת, אין צורך לשמור את ה-URL המקורי ולהפנות אחרי ההתחברות (אם כן רוצים את זה — תצטרכו לממש בעצמכם, אבל זה edge case יחסית נדיר).
// app.json — הגדרת scheme ל-deep linking
{
"expo": {
"scheme": "my-auth-app",
"plugins": [
[
"expo-router",
{
"origin": "https://myapp.example.com"
}
]
]
}
}
עם ההגדרה הזו, deep links כמו my-auth-app://(app)/profile או https://myapp.example.com/(app)/profile יעבדו אוטומטית — ויישמרו על ההגנות שהגדרתם.
שיטות עבודה מומלצות לאבטחה
הגנה על routes בצד הלקוח היא רק חלק אחד מהפאזל. חשוב להבין את זה — Stack.Protected הוא לא תחליף לאבטחה אמיתית בצד השרת. הנה כמה עקרונות שכדאי להקפיד עליהם:
- אימות בצד השרת תמיד —
Stack.Protectedהוא הגנה בצד הלקוח בלבד. תמיד בדקו טוקנים ו-session בצד השרת לכל בקשת API. לעולם אל תסמכו רק על הגנת הלקוח. - רענון טוקנים — השתמשו במנגנון refresh token כדי לא לדרוש מהמשתמשים להתחבר מחדש כל הזמן. שמרו גם את ה-refresh token ב-
expo-secure-store. - ניקוי זיכרון — אל תשאירו טוקנים במשתני state גלובליים לאחר שימוש. נקו הפניות לנתונים רגישים כשהם לא נחוצים יותר.
- HTTPS תמיד — כל התקשורת עם השרת חייבת להיות מוצפנת. אל תשלחו טוקנים או credentials בחיבור HTTP רגיל.
- הגבלת גודל — זכרו ש-
expo-secure-storeמוגבל לכ-2048 בתים ב-iOS. אם יש לכם payload גדול, תצטרכו לפצל אותו או לשמור רק את מה שבאמת צריך.
הנה דוגמה מעשית למנגנון רענון טוקן אוטומטי, שלדעתי הוא must-have בכל אפליקציה שמתכוונת לפרודקשן:
// דוגמה — רענון טוקן אוטומטי
import * as SecureStore from 'expo-secure-store';
async function refreshAccessToken(): Promise<string | null> {
const refreshToken = await SecureStore.getItemAsync('refreshToken');
if (!refreshToken) return null;
try {
const response = await fetch('https://api.example.com/auth/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refreshToken }),
});
if (!response.ok) return null;
const data = await response.json();
await SecureStore.setItemAsync('session',
JSON.stringify({ token: data.accessToken, role: data.role })
);
return data.accessToken;
} catch {
return null;
}
}
// שימוש — wrapping fetch עם רענון אוטומטי
export async function authenticatedFetch(
url: string,
options: RequestInit = {}
): Promise<Response> {
const sessionStr = await SecureStore.getItemAsync('session');
const session = sessionStr ? JSON.parse(sessionStr) : null;
let response = await fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${session?.token}`,
},
});
if (response.status === 401) {
const newToken = await refreshAccessToken();
if (newToken) {
response = await fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${newToken}`,
},
});
}
}
return response;
}
מעבר מ-SDK 52 ומטה ל-Stack.Protected
אם יש לכם אפליקציה קיימת שמשתמשת בשיטת ה-redirect הישנה, המעבר הוא פשוט יחסית. מניסיון שלי, זה לוקח בערך שעה-שעתיים לאפליקציה בינונית. הנה השלבים:
- שדרגו ל-SDK 53 — הריצו
npx expo install expo@latestועקבו אחרי מדריך השדרוג הרשמי. - העבירו את לוגיקת האימות ל-layout הראשי — רכזו את כל ה-guards בקובץ
app/_layout.tsx. - הסירו redirects מ-layouts פנימיים — מחקו כל שימוש ב-
<Redirect />שקשור לאימות בתוך קבצי layout של segments. - בדקו deep links — ודאו שכל ה-deep links עובדים כמצופה עם ההגנות החדשות.
- בדיקת edge cases — ודאו שהתנתקות מנקה היסטוריה כמצופה ושאין הבזקה של מסכים מוגנים.
שאלות נפוצות
האם Stack.Protected מגן מפני גישה ישירה ל-URL?
לא לגמרי. Stack.Protected הוא מנגנון הגנה בצד הלקוח בלבד. בזמן Server-Side Generation, לא נוצרים קבצי HTML עבור routes מוגנים. אבל אם למשתמש יש ידע טכני, הוא עדיין יכול לבקש את קבצי ה-JavaScript ישירות. לכן — ושוב אני חוזר על זה כי זה כל כך חשוב — שלבו תמיד אימות בצד השרת.
מה קורה כשמשתמש מנסה לנווט ל-route מוגן?
הניווט פשוט נכשל בשקט. המשתמש נשאר במסך הנוכחי או מופנה למסך העוגן (anchor route). אין שגיאה, אין crash, אין הבזקה. זה שיפור משמעותי לעומת השיטה הישנה של redirects ידניים שהייתה גורמת ל-flash מעצבן.
איך מטפלים ב-session שפג תוקפו תוך כדי שימוש באפליקציה?
כשה-guard משתנה מ-true ל-false (למשל כשה-session נמחק), המשתמש מופנה אוטומטית מכל ה-routes המוגנים. מומלץ לשלב את מנגנון ה-refresh token שהראיתי למעלה — ככה ה-session מתרענן ברקע לפני שהוא פג, והמשתמש לא מופנה בפתאומיות.
האם אפשר להשתמש ב-Stack.Protected גם עם Drawer Navigator?
כן, בהחלט. בנוסף ל-Stack.Protected ו-Tabs.Protected, Expo Router תומך גם ב-Drawer.Protected באותו אופן בדיוק — מגדירים guard ופריטי התפריט מופיעים או נעלמים בהתאם.
האם צריך לשנות את מבנה התיקיות כדי לעבור ל-Stack.Protected?
לא בהכרח. מבנה התיקיות יכול להישאר כמו שהוא. השינוי העיקרי הוא ב-layout הראשי שבו מגדירים את ה-guards. אם כבר יש לכם הפרדה לקבוצות כמו (auth) ו-(app), המעבר הוא מהיר במיוחד — פשוט עוטפים את ה-screens ב-Stack.Protected ומוחקים את ה-redirects הישנים.