@mr.dj2u/knowledge 0.1.0
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/ship-test-loop.md +16 -0
- package/dist/content/checklists/unified-agent-bundle-validation.md +8 -0
- package/dist/content/examples/ship-test-loop.md +13 -0
- package/dist/content/examples/unified-agent-bundle-bootstrap.md +8 -0
- package/dist/content/guides/animation-performance.md +30 -0
- package/dist/content/guides/post-create-onboarding.md +113 -0
- package/dist/content/patterns/api/api-routes.md +314 -0
- package/dist/content/patterns/api/error-handling.md +311 -0
- package/dist/content/patterns/database/drizzle-schema.md +280 -0
- package/dist/content/patterns/database/migrations.md +365 -0
- package/dist/content/patterns/database/query-organization.md +537 -0
- package/dist/content/patterns/database/relations.md +450 -0
- package/dist/content/patterns/deployment/build-configuration.md +452 -0
- package/dist/content/patterns/deployment/ci-cd-patterns.md +448 -0
- package/dist/content/patterns/deployment/environment-config.md +380 -0
- package/dist/content/patterns/deployment/hosting-setup.md +425 -0
- package/dist/content/patterns/project/configuration-patterns.md +459 -0
- package/dist/content/patterns/project/documentation-org.md +506 -0
- package/dist/content/patterns/project/folder-structure.md +398 -0
- package/dist/content/patterns/project/library-exports.md +465 -0
- package/dist/content/patterns/project/monorepo-structure.md +500 -0
- package/dist/content/patterns/routing/dynamic-routes.md +221 -0
- package/dist/content/patterns/routing/file-based-routing.md +186 -0
- package/dist/content/patterns/routing/route-groups.md +429 -0
- package/dist/content/patterns/state/persistence-middleware.md +521 -0
- package/dist/content/patterns/state/selector-hooks.md +538 -0
- package/dist/content/patterns/state/store-organization.md +539 -0
- package/dist/content/patterns/state/zustand-patterns.md +348 -0
- package/dist/content/patterns/styling/component-styling.md +468 -0
- package/dist/content/patterns/styling/responsive-patterns.md +398 -0
- package/dist/content/patterns/styling/theme-configuration.md +426 -0
- package/dist/content/patterns/styling/uniwind-setup.md +412 -0
- package/dist/content/prompts/continue-development.md +27 -0
- package/dist/content/prompts/create-expo-super-stack.md +29 -0
- package/dist/content/prompts/fix-seo.md +29 -0
- package/dist/content/prompts/onboard-new-expo-app.md +11 -0
- package/dist/content/prompts/prepare-deploy.md +29 -0
- package/dist/content/prompts/project-research-plan.md +29 -0
- package/dist/content/prompts/review-expo-project.md +29 -0
- package/dist/content/prompts/run-doctor.md +30 -0
- package/dist/content/prompts/ship-test-loop.md +24 -0
- package/dist/content/reference/create-expo-stack-uniwind.md +29 -0
- package/dist/content/reference/doctor-dogfood.md +42 -0
- package/dist/content/reference/mcp-sdk-transport.md +30 -0
- package/dist/content/reference/package-ci-patterns.md +24 -0
- package/dist/content/reference/reference-repo-evacuation.md +31 -0
- package/dist/content/resource-index.json +67 -0
- package/dist/content/rules/app-folder-architecture.md +13 -0
- package/dist/content/rules/env-hygiene.md +9 -0
- package/dist/content/rules/seo-metadata.md +7 -0
- package/dist/content/rules/ssr-safety.md +9 -0
- package/dist/content/skills/api-routes.md +33 -0
- package/dist/content/skills/continue-development.md +31 -0
- package/dist/content/skills/debugging.md +31 -0
- package/dist/content/skills/deployment.md +32 -0
- package/dist/content/skills/dev-server-management.md +31 -0
- package/dist/content/skills/env-vars.md +32 -0
- package/dist/content/skills/expo-router-architecture.md +33 -0
- package/dist/content/skills/expo-ssr-safety.md +32 -0
- package/dist/content/skills/plugin-creation.md +41 -0
- package/dist/content/skills/production-server-patterns.md +31 -0
- package/dist/content/skills/project-onboarding.md +31 -0
- package/dist/content/skills/research-plan-intake.md +31 -0
- package/dist/content/skills/seo-metadata.md +31 -0
- package/dist/content/skills/super-stack-startup.md +31 -0
- package/dist/content/skills/uniwind-theming.md +32 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +369 -0
- package/dist/index.js.map +1 -0
- package/dist/patterns/index.d.ts +78 -0
- package/dist/patterns/index.d.ts.map +1 -0
- package/dist/patterns/index.js +264 -0
- package/dist/patterns/index.js.map +1 -0
- package/dist/prompts/index.d.ts +35 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +270 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/skills/index.d.ts +3 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +2 -0
- package/dist/skills/index.js.map +1 -0
- package/package.json +50 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# File-Based Routing with Expo Router
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
|
|
5
|
+
File-based routing in Expo Router automatically generates routes from the file structure under the `app/` directory. This pattern eliminates manual route configuration and makes navigation structure immediately visible from the filesystem. Routes are only defined under `app/` — components are NOT split into separate `src/screens/` folders, preventing the duplication problem.
|
|
6
|
+
|
|
7
|
+
## When to Use
|
|
8
|
+
|
|
9
|
+
**Always use** file-based routing for all Expo Router applications:
|
|
10
|
+
- ✅ All page-level components live under `app/` directory structure
|
|
11
|
+
- ✅ Route hierarchy directly mirrors file structure
|
|
12
|
+
- ✅ Route organization is enforced by filesystem constraints
|
|
13
|
+
- ✅ Single source of truth for application navigation
|
|
14
|
+
|
|
15
|
+
## Code Example
|
|
16
|
+
|
|
17
|
+
### File Structure Maps to Routes
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
app/
|
|
21
|
+
├── _layout.tsx → Root layout wrapper (providers, fonts, styling)
|
|
22
|
+
├── index.tsx → / (home page)
|
|
23
|
+
├── about.tsx → /about
|
|
24
|
+
├── (drawer)/ → Route group (no URL segment)
|
|
25
|
+
│ ├── _layout.tsx → Drawer navigator wrapper
|
|
26
|
+
│ ├── home.tsx → /home
|
|
27
|
+
│ └── profile.tsx → /profile
|
|
28
|
+
├── (auth)/ → Route group for authentication
|
|
29
|
+
│ ├── sign-in.tsx → /sign-in
|
|
30
|
+
│ └── sign-up.tsx → /sign-up
|
|
31
|
+
├── events/
|
|
32
|
+
│ ├── index.tsx → /events (list)
|
|
33
|
+
│ └── [id].tsx → /events/:id (detail)
|
|
34
|
+
└── guides/
|
|
35
|
+
├── PLZA/
|
|
36
|
+
│ ├── index.tsx → /guides/PLZA
|
|
37
|
+
│ └── strategies/
|
|
38
|
+
│ └── [id].tsx → /guides/PLZA/strategies/:id
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**From:** PokePages, not-hot-dog, DJsPortfolio (reference Expo app src/app folders)
|
|
42
|
+
|
|
43
|
+
### Root Layout Setup
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// app/_layout.tsx
|
|
47
|
+
import { Stack } from 'expo-router';
|
|
48
|
+
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
49
|
+
import { Provider } from 'zustand';
|
|
50
|
+
import { useAuthStore } from '@/store/authStore';
|
|
51
|
+
|
|
52
|
+
export default function RootLayout() {
|
|
53
|
+
const { isLoading } = useAuthStore();
|
|
54
|
+
|
|
55
|
+
if (isLoading) {
|
|
56
|
+
return <LoadingScreen />;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<GestureHandlerRootView style={{ flex: 1 }}>
|
|
61
|
+
<Stack screenOptions={{ headerShown: false }}>
|
|
62
|
+
<Stack.Screen name="(drawer)" />
|
|
63
|
+
<Stack.Screen name="(auth)" />
|
|
64
|
+
</Stack>
|
|
65
|
+
</GestureHandlerRootView>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Pattern from:** core-monorepo/apps/*/src/app/_layout.tsx, PokePages/src/app/_layout.tsx
|
|
71
|
+
|
|
72
|
+
## Configuration
|
|
73
|
+
|
|
74
|
+
### Setup in `app.json`
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"expo": {
|
|
79
|
+
"plugins": [
|
|
80
|
+
[
|
|
81
|
+
"expo-router",
|
|
82
|
+
{
|
|
83
|
+
"origin": false,
|
|
84
|
+
"asyncRoutes": "development",
|
|
85
|
+
"apiRoutes": true
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
],
|
|
89
|
+
"experiments": {
|
|
90
|
+
"typedRoutes": true
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### TypeScript Configuration
|
|
97
|
+
|
|
98
|
+
Enable typed routes in `tsconfig.json`:
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"compilerOptions": {
|
|
103
|
+
"paths": {
|
|
104
|
+
"@/*": ["./src/*"]
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
"references": [{ "path": "./tsconfig.app.json" }]
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Best Practices
|
|
112
|
+
|
|
113
|
+
### ✅ DO
|
|
114
|
+
|
|
115
|
+
1. **Keep `app/` files lean** — only routing and layout logic
|
|
116
|
+
```typescript
|
|
117
|
+
// app/events/[id].tsx ✅ GOOD
|
|
118
|
+
import { EventDetail } from '@/components/EventDetail';
|
|
119
|
+
import { useLocalSearchParams } from 'expo-router';
|
|
120
|
+
|
|
121
|
+
export default function EventDetailRoute() {
|
|
122
|
+
const { id } = useLocalSearchParams<{ id: string }>();
|
|
123
|
+
return <EventDetail eventId={id} />;
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
2. **Use route groups** for organizational structure without affecting URLs
|
|
128
|
+
```
|
|
129
|
+
(drawer)/ ← drawer navigation group, no URL
|
|
130
|
+
├── (tabs)/ ← tabs group nested in drawer, no URL
|
|
131
|
+
│ ├── home.tsx → /home
|
|
132
|
+
│ └── profile.tsx → /profile
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
3. **Import components from `src/components/`** or `src/screens/`
|
|
136
|
+
```typescript
|
|
137
|
+
import { ProfileScreen } from '@/components/ProfileScreen';
|
|
138
|
+
// NOT: import { ProfileScreen } from './ProfileScreen';
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
4. **Use platform-specific files** for route variations
|
|
142
|
+
```
|
|
143
|
+
[id].tsx ← default (all platforms)
|
|
144
|
+
[id].web.tsx ← web-specific
|
|
145
|
+
[id].native.tsx ← iOS/Android specific
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### ❌ DON'T
|
|
149
|
+
|
|
150
|
+
1. **Don't put complex logic in route files**
|
|
151
|
+
```typescript
|
|
152
|
+
// ❌ BAD
|
|
153
|
+
export default function EventRoute() {
|
|
154
|
+
const [events, setEvents] = useState([]);
|
|
155
|
+
const [loading, setLoading] = useState(false);
|
|
156
|
+
// ... 50+ lines of business logic
|
|
157
|
+
return <View>...</View>;
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
2. **Don't duplicate screens in `src/screens/`** AND reference from `app/`
|
|
162
|
+
```
|
|
163
|
+
❌ app/events.tsx AND src/screens/EventsScreen.tsx
|
|
164
|
+
✅ app/events.tsx imports from @/components/EventsScreen
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
3. **Don't use `require()` for dynamic routing** — let Expo Router handle it
|
|
168
|
+
```typescript
|
|
169
|
+
// ❌ BAD
|
|
170
|
+
const Screen = require(`./screens/${routeName}`);
|
|
171
|
+
|
|
172
|
+
// ✅ GOOD
|
|
173
|
+
router.push(`/events/${eventId}`);
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Related Patterns
|
|
177
|
+
|
|
178
|
+
- [Dynamic Routes](./dynamic-routes.md) — Using `[param].tsx` syntax
|
|
179
|
+
- [Route Groups](./route-groups.md) — Organizing routes with `(group)/` syntax
|
|
180
|
+
- [Navigation Patterns](./navigation-patterns.md) — router.push, Link components, deep linking
|
|
181
|
+
- [API Routes](./api-routes.md) — `+api.ts` catch-all pattern for server endpoints
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
*Pattern extracted from production repositories: PokePages, not-hot-dog, DJsPortfolio, core-monorepo*
|
|
186
|
+
*File structure observed in: reference Expo app src/app folders
|
|
@@ -0,0 +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*
|
|
429
|
+
*Files: src/app/ directory structures from Expo Router projects*
|