@mr.dj2u/knowledge 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/content/checklists/push-merge-loop.md +17 -17
- package/dist/content/checklists/unified-agent-bundle-validation.md +9 -9
- package/dist/content/examples/push-merge-loop.md +14 -14
- package/dist/content/examples/unified-agent-bundle-bootstrap.md +8 -8
- package/dist/content/guides/animation-performance.md +31 -1
- package/dist/content/guides/post-create-onboarding.md +140 -140
- package/dist/content/patterns/animation/animation-motion-selection.md +155 -0
- package/dist/content/patterns/api/api-routes.md +313 -313
- package/dist/content/patterns/api/error-handling.md +310 -310
- package/dist/content/patterns/database/drizzle-schema.md +279 -279
- package/dist/content/patterns/database/migrations.md +364 -364
- package/dist/content/patterns/database/query-organization.md +536 -536
- package/dist/content/patterns/database/relations.md +449 -449
- package/dist/content/patterns/deployment/build-configuration.md +440 -440
- package/dist/content/patterns/deployment/ci-cd-patterns.md +447 -447
- package/dist/content/patterns/deployment/environment-config.md +379 -379
- package/dist/content/patterns/deployment/hosting-setup.md +424 -424
- package/dist/content/patterns/project/configuration-patterns.md +459 -459
- package/dist/content/patterns/project/documentation-org.md +506 -506
- package/dist/content/patterns/project/folder-structure.md +397 -397
- package/dist/content/patterns/project/library-exports.md +464 -464
- package/dist/content/patterns/project/monorepo-structure.md +500 -500
- package/dist/content/patterns/routing/dynamic-routes.md +220 -220
- package/dist/content/patterns/routing/file-based-routing.md +185 -185
- package/dist/content/patterns/routing/route-groups.md +428 -428
- package/dist/content/patterns/state/persistence-middleware.md +520 -520
- package/dist/content/patterns/state/selector-hooks.md +537 -537
- package/dist/content/patterns/state/store-organization.md +538 -538
- package/dist/content/patterns/state/zustand-patterns.md +347 -347
- package/dist/content/patterns/styling/component-styling.md +467 -467
- package/dist/content/patterns/styling/responsive-patterns.md +397 -397
- package/dist/content/patterns/styling/theme-configuration.md +425 -425
- package/dist/content/patterns/styling/uniwind-setup.md +411 -411
- package/dist/content/prompts/continue-development.md +35 -35
- package/dist/content/prompts/fix-seo.md +29 -29
- package/dist/content/prompts/onboard-new-expo-app.md +11 -11
- package/dist/content/prompts/prepare-deploy.md +29 -29
- package/dist/content/prompts/project-research-plan.md +29 -29
- package/dist/content/prompts/push-merge-loop.md +25 -25
- package/dist/content/prompts/review-expo-project.md +29 -29
- package/dist/content/prompts/review-motion.md +58 -0
- package/dist/content/prompts/run-doctor.md +38 -38
- package/dist/content/prompts/wrap-up.md +67 -67
- package/dist/content/reference/create-expo-stack-uniwind.md +29 -29
- package/dist/content/reference/mcp-sdk-transport.md +30 -30
- package/dist/content/reference/reference-repo-evacuation.md +31 -31
- package/dist/content/resource-index.json +3 -0
- package/dist/content/skills/animation-motion.md +68 -0
- package/dist/content/skills/api-routes.md +33 -33
- package/dist/content/skills/continue-development.md +32 -32
- package/dist/content/skills/debugging.md +32 -32
- package/dist/content/skills/deployment.md +32 -32
- package/dist/content/skills/dev-server-management.md +32 -32
- package/dist/content/skills/env-vars.md +32 -32
- package/dist/content/skills/expo-router-architecture.md +33 -33
- package/dist/content/skills/expo-ssr-safety.md +32 -32
- package/dist/content/skills/plugin-creation.md +41 -41
- package/dist/content/skills/production-server-patterns.md +31 -31
- package/dist/content/skills/project-onboarding.md +31 -31
- package/dist/content/skills/research-plan-intake.md +32 -32
- package/dist/content/skills/seo-metadata.md +31 -31
- package/dist/content/skills/super-stack-startup.md +34 -34
- package/dist/content/skills/uniwind-theming.md +32 -32
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +27 -1
- package/dist/index.js.map +1 -1
- package/dist/patterns/index.d.ts +9 -1
- package/dist/patterns/index.d.ts.map +1 -1
- package/dist/patterns/index.js +17 -0
- package/dist/patterns/index.js.map +1 -1
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +17 -0
- package/dist/prompts/index.js.map +1 -1
- package/package.json +6 -1
|
@@ -1,429 +1,429 @@
|
|
|
1
|
-
# Route Groups Pattern
|
|
2
|
-
|
|
3
|
-
## Description
|
|
4
|
-
|
|
5
|
-
Route groups use parentheses `(groupName)` to organize routes without adding URL segments. Groups enable shared layouts, nested navigation stacks, and logical route organization within Expo Router's file-based routing system.
|
|
6
|
-
|
|
7
|
-
## When to Use
|
|
8
|
-
|
|
9
|
-
**Use route groups** for:
|
|
10
|
-
- ✅ Shared layout wrapping multiple routes without URL changes
|
|
11
|
-
- ✅ Tab navigation stacks (e.g., `(tabs)/home`, `(tabs)/explore`)
|
|
12
|
-
- ✅ Authentication flow separation (`(auth)` vs main app)
|
|
13
|
-
- ✅ Nested drawer navigation within groups
|
|
14
|
-
- ✅ Organizing related features without URL hierarchy
|
|
15
|
-
|
|
16
|
-
## Code Example
|
|
17
|
-
|
|
18
|
-
### Basic Route Group with Shared Layout
|
|
19
|
-
|
|
20
|
-
```typescript
|
|
21
|
-
// File: src/app/(drawer)/_layout.tsx
|
|
22
|
-
import { Drawer } from 'expo-router/drawer';
|
|
23
|
-
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
24
|
-
|
|
25
|
-
export default function DrawerLayout() {
|
|
26
|
-
return (
|
|
27
|
-
<GestureHandlerRootView style={{ flex: 1 }}>
|
|
28
|
-
<Drawer
|
|
29
|
-
screenOptions={{
|
|
30
|
-
headerShown: true,
|
|
31
|
-
drawerType: 'slide',
|
|
32
|
-
}}
|
|
33
|
-
>
|
|
34
|
-
<Drawer.Screen
|
|
35
|
-
name="(tabs)"
|
|
36
|
-
options={{
|
|
37
|
-
title: 'Home',
|
|
38
|
-
drawerIcon: ({ color }) => <HomeIcon color={color} />,
|
|
39
|
-
}}
|
|
40
|
-
/>
|
|
41
|
-
<Drawer.Screen
|
|
42
|
-
name="settings"
|
|
43
|
-
options={{
|
|
44
|
-
title: 'Settings',
|
|
45
|
-
drawerIcon: ({ color }) => <SettingsIcon color={color} />,
|
|
46
|
-
}}
|
|
47
|
-
/>
|
|
48
|
-
</Drawer>
|
|
49
|
-
</GestureHandlerRootView>
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
```typescript
|
|
55
|
-
// File: src/app/(drawer)/(tabs)/_layout.tsx
|
|
56
|
-
import { Tabs } from 'expo-router';
|
|
57
|
-
import { HomeIcon, ExploreIcon, ProfileIcon } from '@/components/Icons';
|
|
58
|
-
|
|
59
|
-
export default function TabLayout() {
|
|
60
|
-
return (
|
|
61
|
-
<Tabs
|
|
62
|
-
screenOptions={{
|
|
63
|
-
headerShown: false,
|
|
64
|
-
tabBarActiveTintColor: '#007AFF',
|
|
65
|
-
}}
|
|
66
|
-
>
|
|
67
|
-
<Tabs.Screen
|
|
68
|
-
name="index"
|
|
69
|
-
options={{
|
|
70
|
-
title: 'Home',
|
|
71
|
-
tabBarIcon: ({ color }) => <HomeIcon color={color} />,
|
|
72
|
-
}}
|
|
73
|
-
/>
|
|
74
|
-
<Tabs.Screen
|
|
75
|
-
name="explore"
|
|
76
|
-
options={{
|
|
77
|
-
title: 'Explore',
|
|
78
|
-
tabBarIcon: ({ color }) => <ExploreIcon color={color} />,
|
|
79
|
-
}}
|
|
80
|
-
/>
|
|
81
|
-
<Tabs.Screen
|
|
82
|
-
name="profile"
|
|
83
|
-
options={{
|
|
84
|
-
title: 'Profile',
|
|
85
|
-
tabBarIcon: ({ color }) => <ProfileIcon color={color} />,
|
|
86
|
-
}}
|
|
87
|
-
/>
|
|
88
|
-
</Tabs>
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
```typescript
|
|
94
|
-
// File: src/app/(drawer)/(tabs)/index.tsx
|
|
95
|
-
import { View, Text } from 'react-native';
|
|
96
|
-
|
|
97
|
-
export default function HomeScreen() {
|
|
98
|
-
return (
|
|
99
|
-
<View className="flex-1 items-center justify-center">
|
|
100
|
-
<Text>Home Tab</Text>
|
|
101
|
-
</View>
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
// Route: /home (no "(tabs)" in URL)
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
### Multiple Nested Groups
|
|
108
|
-
|
|
109
|
-
```
|
|
110
|
-
src/app/
|
|
111
|
-
├── _layout.tsx (root: Stack)
|
|
112
|
-
├── (drawer)/ (drawer group, no URL)
|
|
113
|
-
│ ├── _layout.tsx (Drawer layout)
|
|
114
|
-
│ ├── (tabs)/ (tabs group within drawer, no URL)
|
|
115
|
-
│ │ ├── _layout.tsx (Tabs layout)
|
|
116
|
-
│ │ ├── index.tsx → /
|
|
117
|
-
│ │ ├── explore.tsx → /explore
|
|
118
|
-
│ │ └── profile.tsx → /profile
|
|
119
|
-
│ └── settings.tsx → /settings
|
|
120
|
-
├── (auth)/ (auth group, no URL)
|
|
121
|
-
│ ├── _layout.tsx (Stack layout for auth flow)
|
|
122
|
-
│ ├── sign-in.tsx → /sign-in
|
|
123
|
-
│ └── sign-up.tsx → /sign-up
|
|
124
|
-
└── +not-found.tsx (catch-all 404)
|
|
125
|
-
|
|
126
|
-
// Key insight: Route groups "(drawer)" and "(tabs)" don't appear in URLs
|
|
127
|
-
// Routes map: / (home), /explore, /profile, /settings, /sign-in, /sign-up
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
### Authentication Flow with Route Groups
|
|
131
|
-
|
|
132
|
-
```typescript
|
|
133
|
-
// File: src/app/_layout.tsx (Root Layout)
|
|
134
|
-
import { Stack } from 'expo-router';
|
|
135
|
-
import { useAuthStore } from '@/store/authStore';
|
|
136
|
-
|
|
137
|
-
export default function RootLayout() {
|
|
138
|
-
const { isLoggedIn, loading } = useAuthStore();
|
|
139
|
-
|
|
140
|
-
if (loading) {
|
|
141
|
-
return <LoadingScreen />;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return (
|
|
145
|
-
<Stack
|
|
146
|
-
screenOptions={{
|
|
147
|
-
headerShown: false,
|
|
148
|
-
animationEnabled: true,
|
|
149
|
-
}}
|
|
150
|
-
>
|
|
151
|
-
{isLoggedIn ? (
|
|
152
|
-
// Main app routes (drawer + tabs)
|
|
153
|
-
<Stack.Screen name="(drawer)" />
|
|
154
|
-
) : (
|
|
155
|
-
// Auth routes (sign-in, sign-up)
|
|
156
|
-
<Stack.Screen
|
|
157
|
-
name="(auth)"
|
|
158
|
-
options={{
|
|
159
|
-
animationEnabled: false,
|
|
160
|
-
}}
|
|
161
|
-
/>
|
|
162
|
-
)}
|
|
163
|
-
</Stack>
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
```typescript
|
|
169
|
-
// File: src/app/(auth)/_layout.tsx
|
|
170
|
-
import { Stack } from 'expo-router';
|
|
171
|
-
|
|
172
|
-
export default function AuthLayout() {
|
|
173
|
-
return (
|
|
174
|
-
<Stack
|
|
175
|
-
screenOptions={{
|
|
176
|
-
headerShown: false,
|
|
177
|
-
animationEnabled: true,
|
|
178
|
-
}}
|
|
179
|
-
>
|
|
180
|
-
<Stack.Screen name="sign-in" />
|
|
181
|
-
<Stack.Screen name="sign-up" />
|
|
182
|
-
</Stack>
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
```typescript
|
|
188
|
-
// File: src/app/(auth)/sign-in.tsx
|
|
189
|
-
import { View, Text, Pressable } from 'react-native';
|
|
190
|
-
import { router } from 'expo-router';
|
|
191
|
-
|
|
192
|
-
export default function SignInScreen() {
|
|
193
|
-
const handleSignIn = async () => {
|
|
194
|
-
// Authentication logic
|
|
195
|
-
await authenticate();
|
|
196
|
-
// Replace entire stack with main app
|
|
197
|
-
router.replace('/(drawer)/(tabs)/');
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
return (
|
|
201
|
-
<View className="flex-1 justify-center items-center">
|
|
202
|
-
<Text>Sign In</Text>
|
|
203
|
-
<Pressable onPress={handleSignIn}>
|
|
204
|
-
<Text>Login</Text>
|
|
205
|
-
</Pressable>
|
|
206
|
-
</View>
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
// Route: /sign-in (not /(auth)/sign-in)
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
### Feature-Specific Groups
|
|
213
|
-
|
|
214
|
-
```
|
|
215
|
-
src/app/
|
|
216
|
-
├── (drawer)/
|
|
217
|
-
│ ├── _layout.tsx
|
|
218
|
-
│ ├── (tabs)/
|
|
219
|
-
│ │ ├── _layout.tsx
|
|
220
|
-
│ │ ├── index.tsx
|
|
221
|
-
│ │ └── explore.tsx
|
|
222
|
-
│ └── (guides)/ (guides feature group)
|
|
223
|
-
│ ├── _layout.tsx (guides layout/header)
|
|
224
|
-
│ ├── index.tsx → /guides
|
|
225
|
-
│ ├── [id].tsx → /guides/[id]
|
|
226
|
-
│ └── search.tsx → /guides/search
|
|
227
|
-
└── (modal)/ (modal overlay group)
|
|
228
|
-
├── _layout.tsx (modal presentation)
|
|
229
|
-
├── settings-modal.tsx → /settings-modal
|
|
230
|
-
└── share-modal.tsx → /share-modal
|
|
231
|
-
|
|
232
|
-
// Routes: /guides, /guides/123, /guides/search, /settings-modal, /share-modal
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
```typescript
|
|
236
|
-
// File: src/app/(drawer)/(guides)/_layout.tsx
|
|
237
|
-
import { Stack } from 'expo-router';
|
|
238
|
-
|
|
239
|
-
export default function GuidesLayout() {
|
|
240
|
-
return (
|
|
241
|
-
<Stack
|
|
242
|
-
screenOptions={{
|
|
243
|
-
headerShown: true,
|
|
244
|
-
headerTitle: 'Guides',
|
|
245
|
-
headerBackVisible: true,
|
|
246
|
-
}}
|
|
247
|
-
>
|
|
248
|
-
<Stack.Screen name="index" />
|
|
249
|
-
<Stack.Screen name="[id]" />
|
|
250
|
-
<Stack.Screen name="search" />
|
|
251
|
-
</Stack>
|
|
252
|
-
);
|
|
253
|
-
}
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
### Modal Presentation with Route Groups
|
|
257
|
-
|
|
258
|
-
```typescript
|
|
259
|
-
// File: src/app/_layout.tsx
|
|
260
|
-
import { Stack } from 'expo-router';
|
|
261
|
-
|
|
262
|
-
export default function RootLayout() {
|
|
263
|
-
return (
|
|
264
|
-
<Stack>
|
|
265
|
-
{/* Main app */}
|
|
266
|
-
<Stack.Screen name="(drawer)" />
|
|
267
|
-
|
|
268
|
-
{/* Modal stack (presented on top) */}
|
|
269
|
-
<Stack.Screen
|
|
270
|
-
name="(modal)"
|
|
271
|
-
options={{
|
|
272
|
-
presentation: 'modal',
|
|
273
|
-
headerShown: false,
|
|
274
|
-
animationEnabled: true,
|
|
275
|
-
}}
|
|
276
|
-
/>
|
|
277
|
-
</Stack>
|
|
278
|
-
);
|
|
279
|
-
}
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
```typescript
|
|
283
|
-
// File: src/app/(modal)/_layout.tsx
|
|
284
|
-
import { Stack } from 'expo-router';
|
|
285
|
-
|
|
286
|
-
export default function ModalLayout() {
|
|
287
|
-
return (
|
|
288
|
-
<Stack
|
|
289
|
-
screenOptions={{
|
|
290
|
-
presentation: 'modal',
|
|
291
|
-
}}
|
|
292
|
-
>
|
|
293
|
-
<Stack.Screen name="settings-modal" />
|
|
294
|
-
<Stack.Screen name="share-modal" />
|
|
295
|
-
</Stack>
|
|
296
|
-
);
|
|
297
|
-
}
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
```typescript
|
|
301
|
-
// File: src/app/(modal)/settings-modal.tsx
|
|
302
|
-
import { View, Text, Pressable } from 'react-native';
|
|
303
|
-
import { router } from 'expo-router';
|
|
304
|
-
|
|
305
|
-
export default function SettingsModal() {
|
|
306
|
-
return (
|
|
307
|
-
<View className="flex-1 bg-white rounded-t-2xl p-4">
|
|
308
|
-
<View className="flex-row justify-between items-center mb-4">
|
|
309
|
-
<Text className="text-xl font-bold">Settings</Text>
|
|
310
|
-
<Pressable onPress={() => router.back()}>
|
|
311
|
-
<Text className="text-lg">✕</Text>
|
|
312
|
-
</Pressable>
|
|
313
|
-
</View>
|
|
314
|
-
|
|
315
|
-
{/* Settings content */}
|
|
316
|
-
</View>
|
|
317
|
-
);
|
|
318
|
-
}
|
|
319
|
-
// Route: /settings-modal (presented as modal)
|
|
320
|
-
// Navigate: router.push('/settings-modal')
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
## Route Group Naming Conventions
|
|
324
|
-
|
|
325
|
-
| Pattern | Purpose | URL Impact |
|
|
326
|
-
|---------|---------|-----------|
|
|
327
|
-
| `(tabs)` | Tab navigation group | No URL segment |
|
|
328
|
-
| `(drawer)` | Drawer navigation group | No URL segment |
|
|
329
|
-
| `(auth)` | Authentication flow | No URL segment |
|
|
330
|
-
| `(modal)` | Modal overlays | No URL segment |
|
|
331
|
-
| `(feature-name)` | Feature-specific routes | No URL segment |
|
|
332
|
-
|
|
333
|
-
## Navigation Patterns
|
|
334
|
-
|
|
335
|
-
### Navigate Between Groups
|
|
336
|
-
|
|
337
|
-
```typescript
|
|
338
|
-
import { router } from 'expo-router';
|
|
339
|
-
|
|
340
|
-
// Navigate to route in different group
|
|
341
|
-
router.push('/'); // Home in (tabs)
|
|
342
|
-
router.push('/guides'); // Guides in (guides)
|
|
343
|
-
router.push('/settings-modal'); // Modal in (modal)
|
|
344
|
-
|
|
345
|
-
// Navigate with params
|
|
346
|
-
router.push('/guides/123');
|
|
347
|
-
router.push({
|
|
348
|
-
pathname: '/guides/[id]',
|
|
349
|
-
params: { id: '123' },
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
// Replace (clear stack)
|
|
353
|
-
router.replace('/(drawer)/(tabs)/');
|
|
354
|
-
router.replace('/(auth)/sign-in');
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
## Best Practices
|
|
358
|
-
|
|
359
|
-
### ✅ DO
|
|
360
|
-
|
|
361
|
-
1. **Use groups to organize related routes**
|
|
362
|
-
```
|
|
363
|
-
(drawer)/ - Main app navigation
|
|
364
|
-
(tabs)/ - Tab routes
|
|
365
|
-
(auth)/ - Auth flow
|
|
366
|
-
(guides)/ - Feature routes
|
|
367
|
-
```
|
|
368
|
-
|
|
369
|
-
2. **Keep _layout.tsx files focused** on navigation setup
|
|
370
|
-
```typescript
|
|
371
|
-
export default function GuideLayout() {
|
|
372
|
-
return (
|
|
373
|
-
<Stack screenOptions={{ /* navigation options */ }}>
|
|
374
|
-
<Stack.Screen name="index" />
|
|
375
|
-
<Stack.Screen name="[id]" />
|
|
376
|
-
</Stack>
|
|
377
|
-
);
|
|
378
|
-
}
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
3. **Use router.replace()** when changing auth states
|
|
382
|
-
```typescript
|
|
383
|
-
// After login, replace auth stack with main app
|
|
384
|
-
router.replace('/(drawer)/(tabs)/');
|
|
385
|
-
|
|
386
|
-
// After logout, replace main app with auth
|
|
387
|
-
router.replace('/(auth)/sign-in');
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
### ❌ DON'T
|
|
391
|
-
|
|
392
|
-
1. **Don't nest groups excessively**
|
|
393
|
-
```typescript
|
|
394
|
-
// ❌ TOO DEEP
|
|
395
|
-
(app)/(main)/(features)/(guides)/index.tsx
|
|
396
|
-
|
|
397
|
-
// ✅ REASONABLE
|
|
398
|
-
(drawer)/(guides)/index.tsx
|
|
399
|
-
```
|
|
400
|
-
|
|
401
|
-
2. **Don't duplicate layout logic**
|
|
402
|
-
```typescript
|
|
403
|
-
// ❌ DUPLICATED
|
|
404
|
-
// (drawer)/_layout.tsx - defines Drawer
|
|
405
|
-
// (drawer)/(tabs)/_layout.tsx - defines Drawer again
|
|
406
|
-
|
|
407
|
-
// ✅ COMPOSED
|
|
408
|
-
// (drawer)/_layout.tsx - wraps (tabs) group
|
|
409
|
-
// (drawer)/(tabs)/_layout.tsx - defines Tabs within drawer
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
3. **Don't forget router.replace()** for auth transitions
|
|
413
|
-
```typescript
|
|
414
|
-
// ❌ LEAVES BACK BUTTON
|
|
415
|
-
router.push('/(drawer)/(tabs)/');
|
|
416
|
-
|
|
417
|
-
// ✅ CLEAR STACK
|
|
418
|
-
router.replace('/(drawer)/(tabs)/');
|
|
419
|
-
```
|
|
420
|
-
|
|
421
|
-
## Related Patterns
|
|
422
|
-
|
|
423
|
-
- [File-Based Routing](./file-based-routing.md) — Routing fundamentals
|
|
424
|
-
- [Dynamic Routes](./dynamic-routes.md) — Dynamic segments
|
|
425
|
-
|
|
426
|
-
---
|
|
427
|
-
|
|
428
|
-
*Pattern extracted from production repositories: core-monorepo, PokePages, DJsPortfolio*
|
|
1
|
+
# Route Groups Pattern
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
|
|
5
|
+
Route groups use parentheses `(groupName)` to organize routes without adding URL segments. Groups enable shared layouts, nested navigation stacks, and logical route organization within Expo Router's file-based routing system.
|
|
6
|
+
|
|
7
|
+
## When to Use
|
|
8
|
+
|
|
9
|
+
**Use route groups** for:
|
|
10
|
+
- ✅ Shared layout wrapping multiple routes without URL changes
|
|
11
|
+
- ✅ Tab navigation stacks (e.g., `(tabs)/home`, `(tabs)/explore`)
|
|
12
|
+
- ✅ Authentication flow separation (`(auth)` vs main app)
|
|
13
|
+
- ✅ Nested drawer navigation within groups
|
|
14
|
+
- ✅ Organizing related features without URL hierarchy
|
|
15
|
+
|
|
16
|
+
## Code Example
|
|
17
|
+
|
|
18
|
+
### Basic Route Group with Shared Layout
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// File: src/app/(drawer)/_layout.tsx
|
|
22
|
+
import { Drawer } from 'expo-router/drawer';
|
|
23
|
+
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
24
|
+
|
|
25
|
+
export default function DrawerLayout() {
|
|
26
|
+
return (
|
|
27
|
+
<GestureHandlerRootView style={{ flex: 1 }}>
|
|
28
|
+
<Drawer
|
|
29
|
+
screenOptions={{
|
|
30
|
+
headerShown: true,
|
|
31
|
+
drawerType: 'slide',
|
|
32
|
+
}}
|
|
33
|
+
>
|
|
34
|
+
<Drawer.Screen
|
|
35
|
+
name="(tabs)"
|
|
36
|
+
options={{
|
|
37
|
+
title: 'Home',
|
|
38
|
+
drawerIcon: ({ color }) => <HomeIcon color={color} />,
|
|
39
|
+
}}
|
|
40
|
+
/>
|
|
41
|
+
<Drawer.Screen
|
|
42
|
+
name="settings"
|
|
43
|
+
options={{
|
|
44
|
+
title: 'Settings',
|
|
45
|
+
drawerIcon: ({ color }) => <SettingsIcon color={color} />,
|
|
46
|
+
}}
|
|
47
|
+
/>
|
|
48
|
+
</Drawer>
|
|
49
|
+
</GestureHandlerRootView>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
// File: src/app/(drawer)/(tabs)/_layout.tsx
|
|
56
|
+
import { Tabs } from 'expo-router';
|
|
57
|
+
import { HomeIcon, ExploreIcon, ProfileIcon } from '@/components/Icons';
|
|
58
|
+
|
|
59
|
+
export default function TabLayout() {
|
|
60
|
+
return (
|
|
61
|
+
<Tabs
|
|
62
|
+
screenOptions={{
|
|
63
|
+
headerShown: false,
|
|
64
|
+
tabBarActiveTintColor: '#007AFF',
|
|
65
|
+
}}
|
|
66
|
+
>
|
|
67
|
+
<Tabs.Screen
|
|
68
|
+
name="index"
|
|
69
|
+
options={{
|
|
70
|
+
title: 'Home',
|
|
71
|
+
tabBarIcon: ({ color }) => <HomeIcon color={color} />,
|
|
72
|
+
}}
|
|
73
|
+
/>
|
|
74
|
+
<Tabs.Screen
|
|
75
|
+
name="explore"
|
|
76
|
+
options={{
|
|
77
|
+
title: 'Explore',
|
|
78
|
+
tabBarIcon: ({ color }) => <ExploreIcon color={color} />,
|
|
79
|
+
}}
|
|
80
|
+
/>
|
|
81
|
+
<Tabs.Screen
|
|
82
|
+
name="profile"
|
|
83
|
+
options={{
|
|
84
|
+
title: 'Profile',
|
|
85
|
+
tabBarIcon: ({ color }) => <ProfileIcon color={color} />,
|
|
86
|
+
}}
|
|
87
|
+
/>
|
|
88
|
+
</Tabs>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
// File: src/app/(drawer)/(tabs)/index.tsx
|
|
95
|
+
import { View, Text } from 'react-native';
|
|
96
|
+
|
|
97
|
+
export default function HomeScreen() {
|
|
98
|
+
return (
|
|
99
|
+
<View className="flex-1 items-center justify-center">
|
|
100
|
+
<Text>Home Tab</Text>
|
|
101
|
+
</View>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
// Route: /home (no "(tabs)" in URL)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Multiple Nested Groups
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
src/app/
|
|
111
|
+
├── _layout.tsx (root: Stack)
|
|
112
|
+
├── (drawer)/ (drawer group, no URL)
|
|
113
|
+
│ ├── _layout.tsx (Drawer layout)
|
|
114
|
+
│ ├── (tabs)/ (tabs group within drawer, no URL)
|
|
115
|
+
│ │ ├── _layout.tsx (Tabs layout)
|
|
116
|
+
│ │ ├── index.tsx → /
|
|
117
|
+
│ │ ├── explore.tsx → /explore
|
|
118
|
+
│ │ └── profile.tsx → /profile
|
|
119
|
+
│ └── settings.tsx → /settings
|
|
120
|
+
├── (auth)/ (auth group, no URL)
|
|
121
|
+
│ ├── _layout.tsx (Stack layout for auth flow)
|
|
122
|
+
│ ├── sign-in.tsx → /sign-in
|
|
123
|
+
│ └── sign-up.tsx → /sign-up
|
|
124
|
+
└── +not-found.tsx (catch-all 404)
|
|
125
|
+
|
|
126
|
+
// Key insight: Route groups "(drawer)" and "(tabs)" don't appear in URLs
|
|
127
|
+
// Routes map: / (home), /explore, /profile, /settings, /sign-in, /sign-up
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Authentication Flow with Route Groups
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
// File: src/app/_layout.tsx (Root Layout)
|
|
134
|
+
import { Stack } from 'expo-router';
|
|
135
|
+
import { useAuthStore } from '@/store/authStore';
|
|
136
|
+
|
|
137
|
+
export default function RootLayout() {
|
|
138
|
+
const { isLoggedIn, loading } = useAuthStore();
|
|
139
|
+
|
|
140
|
+
if (loading) {
|
|
141
|
+
return <LoadingScreen />;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<Stack
|
|
146
|
+
screenOptions={{
|
|
147
|
+
headerShown: false,
|
|
148
|
+
animationEnabled: true,
|
|
149
|
+
}}
|
|
150
|
+
>
|
|
151
|
+
{isLoggedIn ? (
|
|
152
|
+
// Main app routes (drawer + tabs)
|
|
153
|
+
<Stack.Screen name="(drawer)" />
|
|
154
|
+
) : (
|
|
155
|
+
// Auth routes (sign-in, sign-up)
|
|
156
|
+
<Stack.Screen
|
|
157
|
+
name="(auth)"
|
|
158
|
+
options={{
|
|
159
|
+
animationEnabled: false,
|
|
160
|
+
}}
|
|
161
|
+
/>
|
|
162
|
+
)}
|
|
163
|
+
</Stack>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
// File: src/app/(auth)/_layout.tsx
|
|
170
|
+
import { Stack } from 'expo-router';
|
|
171
|
+
|
|
172
|
+
export default function AuthLayout() {
|
|
173
|
+
return (
|
|
174
|
+
<Stack
|
|
175
|
+
screenOptions={{
|
|
176
|
+
headerShown: false,
|
|
177
|
+
animationEnabled: true,
|
|
178
|
+
}}
|
|
179
|
+
>
|
|
180
|
+
<Stack.Screen name="sign-in" />
|
|
181
|
+
<Stack.Screen name="sign-up" />
|
|
182
|
+
</Stack>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
// File: src/app/(auth)/sign-in.tsx
|
|
189
|
+
import { View, Text, Pressable } from 'react-native';
|
|
190
|
+
import { router } from 'expo-router';
|
|
191
|
+
|
|
192
|
+
export default function SignInScreen() {
|
|
193
|
+
const handleSignIn = async () => {
|
|
194
|
+
// Authentication logic
|
|
195
|
+
await authenticate();
|
|
196
|
+
// Replace entire stack with main app
|
|
197
|
+
router.replace('/(drawer)/(tabs)/');
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
return (
|
|
201
|
+
<View className="flex-1 justify-center items-center">
|
|
202
|
+
<Text>Sign In</Text>
|
|
203
|
+
<Pressable onPress={handleSignIn}>
|
|
204
|
+
<Text>Login</Text>
|
|
205
|
+
</Pressable>
|
|
206
|
+
</View>
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
// Route: /sign-in (not /(auth)/sign-in)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Feature-Specific Groups
|
|
213
|
+
|
|
214
|
+
```
|
|
215
|
+
src/app/
|
|
216
|
+
├── (drawer)/
|
|
217
|
+
│ ├── _layout.tsx
|
|
218
|
+
│ ├── (tabs)/
|
|
219
|
+
│ │ ├── _layout.tsx
|
|
220
|
+
│ │ ├── index.tsx
|
|
221
|
+
│ │ └── explore.tsx
|
|
222
|
+
│ └── (guides)/ (guides feature group)
|
|
223
|
+
│ ├── _layout.tsx (guides layout/header)
|
|
224
|
+
│ ├── index.tsx → /guides
|
|
225
|
+
│ ├── [id].tsx → /guides/[id]
|
|
226
|
+
│ └── search.tsx → /guides/search
|
|
227
|
+
└── (modal)/ (modal overlay group)
|
|
228
|
+
├── _layout.tsx (modal presentation)
|
|
229
|
+
├── settings-modal.tsx → /settings-modal
|
|
230
|
+
└── share-modal.tsx → /share-modal
|
|
231
|
+
|
|
232
|
+
// Routes: /guides, /guides/123, /guides/search, /settings-modal, /share-modal
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
// File: src/app/(drawer)/(guides)/_layout.tsx
|
|
237
|
+
import { Stack } from 'expo-router';
|
|
238
|
+
|
|
239
|
+
export default function GuidesLayout() {
|
|
240
|
+
return (
|
|
241
|
+
<Stack
|
|
242
|
+
screenOptions={{
|
|
243
|
+
headerShown: true,
|
|
244
|
+
headerTitle: 'Guides',
|
|
245
|
+
headerBackVisible: true,
|
|
246
|
+
}}
|
|
247
|
+
>
|
|
248
|
+
<Stack.Screen name="index" />
|
|
249
|
+
<Stack.Screen name="[id]" />
|
|
250
|
+
<Stack.Screen name="search" />
|
|
251
|
+
</Stack>
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Modal Presentation with Route Groups
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
// File: src/app/_layout.tsx
|
|
260
|
+
import { Stack } from 'expo-router';
|
|
261
|
+
|
|
262
|
+
export default function RootLayout() {
|
|
263
|
+
return (
|
|
264
|
+
<Stack>
|
|
265
|
+
{/* Main app */}
|
|
266
|
+
<Stack.Screen name="(drawer)" />
|
|
267
|
+
|
|
268
|
+
{/* Modal stack (presented on top) */}
|
|
269
|
+
<Stack.Screen
|
|
270
|
+
name="(modal)"
|
|
271
|
+
options={{
|
|
272
|
+
presentation: 'modal',
|
|
273
|
+
headerShown: false,
|
|
274
|
+
animationEnabled: true,
|
|
275
|
+
}}
|
|
276
|
+
/>
|
|
277
|
+
</Stack>
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
// File: src/app/(modal)/_layout.tsx
|
|
284
|
+
import { Stack } from 'expo-router';
|
|
285
|
+
|
|
286
|
+
export default function ModalLayout() {
|
|
287
|
+
return (
|
|
288
|
+
<Stack
|
|
289
|
+
screenOptions={{
|
|
290
|
+
presentation: 'modal',
|
|
291
|
+
}}
|
|
292
|
+
>
|
|
293
|
+
<Stack.Screen name="settings-modal" />
|
|
294
|
+
<Stack.Screen name="share-modal" />
|
|
295
|
+
</Stack>
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
// File: src/app/(modal)/settings-modal.tsx
|
|
302
|
+
import { View, Text, Pressable } from 'react-native';
|
|
303
|
+
import { router } from 'expo-router';
|
|
304
|
+
|
|
305
|
+
export default function SettingsModal() {
|
|
306
|
+
return (
|
|
307
|
+
<View className="flex-1 bg-white rounded-t-2xl p-4">
|
|
308
|
+
<View className="flex-row justify-between items-center mb-4">
|
|
309
|
+
<Text className="text-xl font-bold">Settings</Text>
|
|
310
|
+
<Pressable onPress={() => router.back()}>
|
|
311
|
+
<Text className="text-lg">✕</Text>
|
|
312
|
+
</Pressable>
|
|
313
|
+
</View>
|
|
314
|
+
|
|
315
|
+
{/* Settings content */}
|
|
316
|
+
</View>
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
// Route: /settings-modal (presented as modal)
|
|
320
|
+
// Navigate: router.push('/settings-modal')
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## Route Group Naming Conventions
|
|
324
|
+
|
|
325
|
+
| Pattern | Purpose | URL Impact |
|
|
326
|
+
|---------|---------|-----------|
|
|
327
|
+
| `(tabs)` | Tab navigation group | No URL segment |
|
|
328
|
+
| `(drawer)` | Drawer navigation group | No URL segment |
|
|
329
|
+
| `(auth)` | Authentication flow | No URL segment |
|
|
330
|
+
| `(modal)` | Modal overlays | No URL segment |
|
|
331
|
+
| `(feature-name)` | Feature-specific routes | No URL segment |
|
|
332
|
+
|
|
333
|
+
## Navigation Patterns
|
|
334
|
+
|
|
335
|
+
### Navigate Between Groups
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
import { router } from 'expo-router';
|
|
339
|
+
|
|
340
|
+
// Navigate to route in different group
|
|
341
|
+
router.push('/'); // Home in (tabs)
|
|
342
|
+
router.push('/guides'); // Guides in (guides)
|
|
343
|
+
router.push('/settings-modal'); // Modal in (modal)
|
|
344
|
+
|
|
345
|
+
// Navigate with params
|
|
346
|
+
router.push('/guides/123');
|
|
347
|
+
router.push({
|
|
348
|
+
pathname: '/guides/[id]',
|
|
349
|
+
params: { id: '123' },
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// Replace (clear stack)
|
|
353
|
+
router.replace('/(drawer)/(tabs)/');
|
|
354
|
+
router.replace('/(auth)/sign-in');
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
## Best Practices
|
|
358
|
+
|
|
359
|
+
### ✅ DO
|
|
360
|
+
|
|
361
|
+
1. **Use groups to organize related routes**
|
|
362
|
+
```
|
|
363
|
+
(drawer)/ - Main app navigation
|
|
364
|
+
(tabs)/ - Tab routes
|
|
365
|
+
(auth)/ - Auth flow
|
|
366
|
+
(guides)/ - Feature routes
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
2. **Keep _layout.tsx files focused** on navigation setup
|
|
370
|
+
```typescript
|
|
371
|
+
export default function GuideLayout() {
|
|
372
|
+
return (
|
|
373
|
+
<Stack screenOptions={{ /* navigation options */ }}>
|
|
374
|
+
<Stack.Screen name="index" />
|
|
375
|
+
<Stack.Screen name="[id]" />
|
|
376
|
+
</Stack>
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
3. **Use router.replace()** when changing auth states
|
|
382
|
+
```typescript
|
|
383
|
+
// After login, replace auth stack with main app
|
|
384
|
+
router.replace('/(drawer)/(tabs)/');
|
|
385
|
+
|
|
386
|
+
// After logout, replace main app with auth
|
|
387
|
+
router.replace('/(auth)/sign-in');
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### ❌ DON'T
|
|
391
|
+
|
|
392
|
+
1. **Don't nest groups excessively**
|
|
393
|
+
```typescript
|
|
394
|
+
// ❌ TOO DEEP
|
|
395
|
+
(app)/(main)/(features)/(guides)/index.tsx
|
|
396
|
+
|
|
397
|
+
// ✅ REASONABLE
|
|
398
|
+
(drawer)/(guides)/index.tsx
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
2. **Don't duplicate layout logic**
|
|
402
|
+
```typescript
|
|
403
|
+
// ❌ DUPLICATED
|
|
404
|
+
// (drawer)/_layout.tsx - defines Drawer
|
|
405
|
+
// (drawer)/(tabs)/_layout.tsx - defines Drawer again
|
|
406
|
+
|
|
407
|
+
// ✅ COMPOSED
|
|
408
|
+
// (drawer)/_layout.tsx - wraps (tabs) group
|
|
409
|
+
// (drawer)/(tabs)/_layout.tsx - defines Tabs within drawer
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
3. **Don't forget router.replace()** for auth transitions
|
|
413
|
+
```typescript
|
|
414
|
+
// ❌ LEAVES BACK BUTTON
|
|
415
|
+
router.push('/(drawer)/(tabs)/');
|
|
416
|
+
|
|
417
|
+
// ✅ CLEAR STACK
|
|
418
|
+
router.replace('/(drawer)/(tabs)/');
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
## Related Patterns
|
|
422
|
+
|
|
423
|
+
- [File-Based Routing](./file-based-routing.md) — Routing fundamentals
|
|
424
|
+
- [Dynamic Routes](./dynamic-routes.md) — Dynamic segments
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
*Pattern extracted from production repositories: core-monorepo, PokePages, DJsPortfolio*
|
|
429
429
|
*Files: src/app/ directory structures from Expo Router projects*
|