@kood/claude-code 0.6.6 → 0.7.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.
Files changed (170) hide show
  1. package/dist/index.js +7 -1
  2. package/package.json +1 -1
  3. package/templates/.claude/agents/analyst.md +5 -0
  4. package/templates/.claude/agents/architect.md +5 -0
  5. package/templates/.claude/agents/build-fixer.md +1 -0
  6. package/templates/.claude/agents/code-reviewer.md +1 -0
  7. package/templates/.claude/agents/critic.md +4 -0
  8. package/templates/.claude/agents/deep-executor.md +1 -0
  9. package/templates/.claude/agents/dependency-manager.md +2 -0
  10. package/templates/.claude/agents/deployment-validator.md +2 -0
  11. package/templates/.claude/agents/designer.md +2 -0
  12. package/templates/.claude/agents/document-writer.md +3 -0
  13. package/templates/.claude/agents/explore.md +1 -0
  14. package/templates/.claude/agents/git-operator.md +2 -0
  15. package/templates/.claude/agents/implementation-executor.md +2 -0
  16. package/templates/.claude/agents/ko-to-en-translator.md +3 -0
  17. package/templates/.claude/agents/lint-fixer.md +2 -0
  18. package/templates/.claude/agents/planner.md +3 -0
  19. package/templates/.claude/agents/pm.md +349 -0
  20. package/templates/.claude/agents/qa-tester.md +1 -0
  21. package/templates/.claude/agents/refactor-advisor.md +4 -0
  22. package/templates/.claude/agents/researcher.md +9 -1
  23. package/templates/.claude/agents/scientist.md +1 -0
  24. package/templates/.claude/agents/security-reviewer.md +1 -0
  25. package/templates/.claude/agents/tdd-guide.md +1 -0
  26. package/templates/.claude/agents/vision.md +1 -0
  27. package/templates/.claude/instructions/agent-patterns/agent-teams-usage.md +376 -0
  28. package/templates/.claude/instructions/sourcing/reliable-search.md +49 -2
  29. package/templates/.claude/scripts/agent-teams/check-availability.sh +238 -0
  30. package/templates/.claude/scripts/agent-teams/setup-tmux.sh +125 -0
  31. package/templates/.claude/skills/agent-teams-setup/SKILL.md +460 -0
  32. package/templates/.claude/skills/brainstorm/SKILL.md +1 -0
  33. package/templates/.claude/skills/bug-fix/SKILL.md +1 -0
  34. package/templates/.claude/skills/crawler/SKILL.md +2 -0
  35. package/templates/.claude/skills/docs-creator/SKILL.md +1 -0
  36. package/templates/.claude/skills/docs-fetch/SKILL.md +6 -4
  37. package/templates/.claude/skills/docs-refactor/SKILL.md +1 -0
  38. package/templates/.claude/skills/elon-musk/SKILL.md +1 -0
  39. package/templates/.claude/skills/execute/SKILL.md +1 -0
  40. package/templates/.claude/skills/feedback/SKILL.md +1 -0
  41. package/templates/.claude/skills/figma-to-code/SKILL.md +1 -0
  42. package/templates/.claude/skills/genius-thinking/SKILL.md +1 -0
  43. package/templates/.claude/skills/global-uiux-design/SKILL.md +1 -0
  44. package/templates/.claude/skills/korea-uiux-design/SKILL.md +1 -0
  45. package/templates/.claude/skills/nextjs-react-best-practices/SKILL.md +1 -0
  46. package/templates/.claude/skills/plan/SKILL.md +1 -0
  47. package/templates/.claude/skills/prd/SKILL.md +1 -0
  48. package/templates/.claude/skills/project-optimizer/AGENTS.md +275 -0
  49. package/templates/.claude/skills/project-optimizer/SKILL.md +375 -0
  50. package/templates/.claude/skills/project-optimizer/rules/arch-config-centralize.md +66 -0
  51. package/templates/.claude/skills/project-optimizer/rules/arch-hot-path.md +35 -0
  52. package/templates/.claude/skills/project-optimizer/rules/arch-interface-segregation.md +51 -0
  53. package/templates/.claude/skills/project-optimizer/rules/arch-module-boundary.md +42 -0
  54. package/templates/.claude/skills/project-optimizer/rules/build-cache.md +57 -0
  55. package/templates/.claude/skills/project-optimizer/rules/build-code-split.md +56 -0
  56. package/templates/.claude/skills/project-optimizer/rules/build-incremental.md +65 -0
  57. package/templates/.claude/skills/project-optimizer/rules/build-minify.md +61 -0
  58. package/templates/.claude/skills/project-optimizer/rules/build-tree-shake.md +60 -0
  59. package/templates/.claude/skills/project-optimizer/rules/code-complexity.md +65 -0
  60. package/templates/.claude/skills/project-optimizer/rules/code-dead-elimination.md +32 -0
  61. package/templates/.claude/skills/project-optimizer/rules/code-duplication.md +54 -0
  62. package/templates/.claude/skills/project-optimizer/rules/code-error-handling.md +75 -0
  63. package/templates/.claude/skills/project-optimizer/rules/code-naming.md +52 -0
  64. package/templates/.claude/skills/project-optimizer/rules/concurrency-defer-await.md +54 -0
  65. package/templates/.claude/skills/project-optimizer/rules/concurrency-parallel.md +90 -0
  66. package/templates/.claude/skills/project-optimizer/rules/concurrency-pipeline.md +68 -0
  67. package/templates/.claude/skills/project-optimizer/rules/concurrency-pool.md +68 -0
  68. package/templates/.claude/skills/project-optimizer/rules/deps-lightweight-alt.md +37 -0
  69. package/templates/.claude/skills/project-optimizer/rules/deps-peer-align.md +44 -0
  70. package/templates/.claude/skills/project-optimizer/rules/deps-security-audit.md +45 -0
  71. package/templates/.claude/skills/project-optimizer/rules/deps-unused-removal.md +25 -0
  72. package/templates/.claude/skills/project-optimizer/rules/deps-version-pin.md +40 -0
  73. package/templates/.claude/skills/project-optimizer/rules/dx-ci-speed.md +47 -0
  74. package/templates/.claude/skills/project-optimizer/rules/dx-dev-server.md +35 -0
  75. package/templates/.claude/skills/project-optimizer/rules/dx-lint-config.md +36 -0
  76. package/templates/.claude/skills/project-optimizer/rules/dx-test-coverage.md +34 -0
  77. package/templates/.claude/skills/project-optimizer/rules/dx-type-safety.md +49 -0
  78. package/templates/.claude/skills/project-optimizer/rules/io-batch-queries.md +67 -0
  79. package/templates/.claude/skills/project-optimizer/rules/io-cache-layer.md +67 -0
  80. package/templates/.claude/skills/project-optimizer/rules/io-connection-reuse.md +67 -0
  81. package/templates/.claude/skills/project-optimizer/rules/io-serialize-minimal.md +61 -0
  82. package/templates/.claude/skills/project-optimizer/rules/io-stream.md +75 -0
  83. package/templates/.claude/skills/project-optimizer/rules/memory-bounded-cache.md +65 -0
  84. package/templates/.claude/skills/project-optimizer/rules/memory-large-data.md +64 -0
  85. package/templates/.claude/skills/project-optimizer/rules/memory-lazy-init.md +78 -0
  86. package/templates/.claude/skills/project-optimizer/rules/memory-leak-prevention.md +79 -0
  87. package/templates/.claude/skills/project-optimizer/rules/memory-pool-reuse.md +70 -0
  88. package/templates/.claude/skills/ralph/SKILL.md +1 -0
  89. package/templates/.claude/skills/refactor/SKILL.md +1 -0
  90. package/templates/.claude/skills/research/SKILL.md +1 -0
  91. package/templates/.claude/skills/sql-optimizer/SKILL.md +438 -0
  92. package/templates/.claude/skills/sql-optimizer/orm-patterns.md +218 -0
  93. package/templates/.claude/skills/startup-validator/SKILL.md +1 -0
  94. package/templates/.claude/skills/tanstack-start-react-best-practices/AGENTS.md +53 -14
  95. package/templates/.claude/skills/tanstack-start-react-best-practices/SKILL.md +94 -27
  96. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/bundle-defer-third-party.md +42 -19
  97. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/client-optimistic-updates.md +109 -0
  98. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/client-suspense-query.md +74 -0
  99. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/client-use-hook.md +81 -0
  100. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/rerender-react-compiler.md +81 -0
  101. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-beforeload-auth.md +121 -0
  102. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-file-conventions.md +104 -0
  103. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-link-navigation.md +119 -0
  104. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-nested-layouts.md +155 -0
  105. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-path-params.md +89 -0
  106. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-pending-component.md +110 -0
  107. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-preload-strategy.md +91 -0
  108. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-router-context.md +120 -0
  109. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-search-params.md +114 -0
  110. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-deferred-data.md +1 -1
  111. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-error-boundaries.md +79 -0
  112. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-middleware.md +85 -0
  113. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-serialization.md +56 -21
  114. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-streaming.md +84 -0
  115. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-validator.md +71 -0
  116. package/templates/.claude/skills/tauri-react-best-practices/AGENTS.md +527 -0
  117. package/templates/.claude/skills/tauri-react-best-practices/SKILL.md +571 -0
  118. package/templates/.claude/skills/tauri-react-best-practices/rules/bundle-barrel-imports.md +140 -0
  119. package/templates/.claude/skills/tauri-react-best-practices/rules/bundle-cargo-profile.md +96 -0
  120. package/templates/.claude/skills/tauri-react-best-practices/rules/bundle-frontend-treeshake.md +242 -0
  121. package/templates/.claude/skills/tauri-react-best-practices/rules/bundle-lazy-components.md +255 -0
  122. package/templates/.claude/skills/tauri-react-best-practices/rules/bundle-remove-unused-commands.md +160 -0
  123. package/templates/.claude/skills/tauri-react-best-practices/rules/deploy-ci-pipeline.md +269 -0
  124. package/templates/.claude/skills/tauri-react-best-practices/rules/deploy-signing.md +207 -0
  125. package/templates/.claude/skills/tauri-react-best-practices/rules/deploy-updater.md +226 -0
  126. package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-async-commands.md +172 -0
  127. package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-batch-commands.md +133 -0
  128. package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-binary-response.md +198 -0
  129. package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-channel-streaming.md +186 -0
  130. package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-error-handling.md +250 -0
  131. package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-type-safe.md +227 -0
  132. package/templates/.claude/skills/tauri-react-best-practices/rules/perf-derived-state.md +231 -0
  133. package/templates/.claude/skills/tauri-react-best-practices/rules/perf-functional-setstate.md +191 -0
  134. package/templates/.claude/skills/tauri-react-best-practices/rules/perf-index-maps.md +276 -0
  135. package/templates/.claude/skills/tauri-react-best-practices/rules/perf-lazy-state-init.md +196 -0
  136. package/templates/.claude/skills/tauri-react-best-practices/rules/plugin-lifecycle.md +265 -0
  137. package/templates/.claude/skills/tauri-react-best-practices/rules/plugin-mobile-compat.md +199 -0
  138. package/templates/.claude/skills/tauri-react-best-practices/rules/plugin-permission-scope.md +193 -0
  139. package/templates/.claude/skills/tauri-react-best-practices/rules/react-error-boundary.md +239 -0
  140. package/templates/.claude/skills/tauri-react-best-practices/rules/react-event-listener.md +151 -0
  141. package/templates/.claude/skills/tauri-react-best-practices/rules/react-file-src.md +155 -0
  142. package/templates/.claude/skills/tauri-react-best-practices/rules/react-invoke-hook.md +139 -0
  143. package/templates/.claude/skills/tauri-react-best-practices/rules/react-optimistic-update.md +211 -0
  144. package/templates/.claude/skills/tauri-react-best-practices/rules/security-capability-split.md +205 -0
  145. package/templates/.claude/skills/tauri-react-best-practices/rules/security-csp.md +207 -0
  146. package/templates/.claude/skills/tauri-react-best-practices/rules/security-least-privilege.md +106 -0
  147. package/templates/.claude/skills/tauri-react-best-practices/rules/security-no-wildcard.md +253 -0
  148. package/templates/.claude/skills/tauri-react-best-practices/rules/security-scope-paths.md +160 -0
  149. package/templates/.claude/skills/tauri-react-best-practices/rules/state-async-mutex.md +270 -0
  150. package/templates/.claude/skills/tauri-react-best-practices/rules/state-mutex-pattern.md +265 -0
  151. package/templates/.claude/skills/tauri-react-best-practices/rules/state-react-sync.md +375 -0
  152. package/templates/.claude/skills/tauri-react-best-practices/rules/state-single-container.md +275 -0
  153. package/templates/tanstack-start/docs/architecture.md +238 -167
  154. package/templates/tanstack-start/docs/library/tanstack-router/error-handling.md +777 -38
  155. package/templates/tanstack-start/docs/library/tanstack-router/hooks.md +549 -37
  156. package/templates/tanstack-start/docs/library/tanstack-router/index.md +895 -111
  157. package/templates/tanstack-start/docs/library/tanstack-router/navigation.md +641 -43
  158. package/templates/tanstack-start/docs/library/tanstack-router/route-context.md +889 -38
  159. package/templates/tanstack-start/docs/library/tanstack-router/search-params.md +891 -29
  160. package/templates/tanstack-start/docs/library/tanstack-start/auth-patterns.md +972 -36
  161. package/templates/tanstack-start/docs/library/tanstack-start/index.md +1525 -881
  162. package/templates/tanstack-start/docs/library/tanstack-start/middleware.md +1099 -20
  163. package/templates/tanstack-start/docs/library/tanstack-start/routing.md +796 -30
  164. package/templates/tanstack-start/docs/library/tanstack-start/server-functions.md +953 -35
  165. package/templates/tanstack-start/docs/library/tanstack-start/setup.md +371 -15
  166. package/templates/tauri/CLAUDE.md +189 -0
  167. package/templates/tauri/docs/guides/distribution.md +261 -0
  168. package/templates/tauri/docs/guides/getting-started.md +302 -0
  169. package/templates/tauri/docs/guides/mobile.md +288 -0
  170. package/templates/tauri/docs/library/tauri/index.md +510 -0
@@ -1,84 +1,935 @@
1
1
  # TanStack Router - Route Context
2
2
 
3
- <patterns>
3
+ > TanStack Router v1.159.4
4
+
5
+ Route context로 라우트 간 상태 공유. 인증, 권한, 사용자 정보, 의존성 주입 등.
6
+
7
+ ---
8
+
9
+ <context_fundamentals>
10
+
11
+ ## Context 기본 개념
12
+
13
+ Context는 라우트를 통해 전달되는 공유 상태. 인증, 쿼리 클라이언트, 권한 등에 활용.
14
+
15
+ ### 3가지 Context 레벨
16
+
17
+ | 레벨 | 정의 위치 | 사용 범위 | 타입 |
18
+ |------|----------|---------|------|
19
+ | Root Context | `createRootRouteWithContext<T>()` | 모든 라우트 | 필수 |
20
+ | Route Context | `beforeLoad` 반환값 | 해당 라우트 + 자식 | 선택 |
21
+ | Pathless Layout | `beforeLoad` + `_authed/` | 자식 라우트만 | 선택 |
22
+
23
+ ### 주요 활용 사례
24
+
25
+ - **의존성 주입**: 데이터 페칭 함수, 쿼리 클라이언트, 뮤테이션 서비스 등
26
+ - **빵 부스러기 (Breadcrumbs)**: 각 라우트에 제목/메타 정보 첨부
27
+ - **동적 메타 태그**: 라우트별 title, description 관리
28
+ - **인증/권한**: 사용자 정보와 권한 전파
29
+
30
+ </context_fundamentals>
31
+
32
+ ---
33
+
34
+ <root_context>
35
+
36
+ ## Root Context: 전역 상태
37
+
38
+ Router 초기화 시 모든 라우트에서 접근 가능한 context.
39
+
40
+ ### 타입 정의
41
+
42
+ ```tsx
43
+ // /src/lib/router-context.ts
44
+ import { QueryClient } from '@tanstack/react-query'
45
+ import { User } from '@/lib/auth'
46
+
47
+ export interface RouterContext {
48
+ queryClient: QueryClient
49
+ auth: {
50
+ isAuthenticated: boolean
51
+ user: User | null
52
+ permissions: string[]
53
+ }
54
+ }
55
+ ```
56
+
57
+ > RouterContext에는 `createRouter`에 직접 전달되는 내용만 포함. beforeLoad에서 추가되는 context는 자동 추론됨.
58
+
59
+ ### Root Route 정의
4
60
 
5
61
  ```tsx
6
- // beforeLoad: 인증 체크 + context 추가
62
+ // /src/routes/__root.tsx
63
+ import { createRootRouteWithContext } from '@tanstack/react-router'
64
+ import { RouterContext } from '@/lib/router-context'
65
+
66
+ export const Route = createRootRouteWithContext<RouterContext>()({
67
+ component: RootLayout,
68
+ notFoundComponent: NotFound,
69
+ })
70
+ // 주의: createRootRouteWithContext는 팩토리 함수라 ()() 이중 호출 필요
71
+
72
+ const RootLayout = () => (
73
+ <div>
74
+ <nav>{/* ... */}</nav>
75
+ <main>
76
+ <Outlet />
77
+ </main>
78
+ </div>
79
+ )
80
+ ```
81
+
82
+ ### Router 초기화
83
+
84
+ ```tsx
85
+ // /src/main.tsx
86
+ import { QueryClient } from '@tanstack/react-query'
87
+ import { createRouter } from '@tanstack/react-router'
88
+ import { routeTree } from './routeTree.gen'
89
+ import { RouterContext } from '@/lib/router-context'
90
+ import { getCurrentUser } from '@/lib/auth'
91
+
92
+ const queryClient = new QueryClient()
93
+
94
+ // 초기 context 설정
95
+ const initialContext: RouterContext = {
96
+ queryClient,
97
+ auth: {
98
+ isAuthenticated: false,
99
+ user: null,
100
+ permissions: [],
101
+ }
102
+ }
103
+
104
+ const router = createRouter({
105
+ routeTree,
106
+ context: initialContext,
107
+ })
108
+
109
+ // 초기 인증 상태 로드
110
+ const user = await getCurrentUser()
111
+ if (user) {
112
+ router.context = {
113
+ ...router.context,
114
+ auth: {
115
+ isAuthenticated: true,
116
+ user,
117
+ permissions: user.permissions,
118
+ },
119
+ }
120
+ }
121
+
122
+ createRoot(document.getElementById('root')).render(
123
+ <RouterProvider router={router} />
124
+ )
125
+ ```
126
+
127
+ ### RouterProvider에서 context 주입 (React 훅 사용)
128
+
129
+ React 훅을 beforeLoad/loader에서 쓸 수 없으므로, RouterProvider의 `context` prop으로 주입.
130
+
131
+ ```tsx
132
+ // /src/router.tsx
133
+ const router = createRouter({
134
+ routeTree,
135
+ context: {
136
+ networkStrength: undefined!, // React에서 나중에 설정
137
+ },
138
+ })
139
+
140
+ // /src/main.tsx
141
+ import { useNetworkStrength } from '@/hooks/useNetworkStrength'
142
+
143
+ function App() {
144
+ const networkStrength = useNetworkStrength()
145
+ // 훅 결과를 context로 주입
146
+ return <RouterProvider router={router} context={{ networkStrength }} />
147
+ }
148
+ ```
149
+
150
+ ```tsx
151
+ // /src/routes/posts.tsx
152
+ export const Route = createFileRoute('/posts')({
153
+ loader: ({ context }) => {
154
+ if (context.networkStrength === 'STRONG') {
155
+ // 고해상도 데이터 로드
156
+ }
157
+ },
158
+ })
159
+ ```
160
+
161
+ ### Root Context 접근
162
+
163
+ ```tsx
164
+ // 모든 라우트에서 접근 가능
165
+ const Dashboard = () => {
166
+ const { user, permissions } = Route.useRouteContext()
167
+
168
+ return (
169
+ <div>
170
+ <h1>Welcome, {user?.name}!</h1>
171
+ {permissions.includes('admin') && <AdminPanel />}
172
+ </div>
173
+ )
174
+ }
175
+ ```
176
+
177
+ </root_context>
178
+
179
+ ---
180
+
181
+ <route_context>
182
+
183
+ ## Route Context: 라우트 레벨 상태
184
+
185
+ 특정 라우트에서 beforeLoad로 context 추가. 자식 라우트도 접근 가능.
186
+
187
+ ### beforeLoad에서 Context 생성
188
+
189
+ ```tsx
190
+ // /src/routes/dashboard.tsx
7
191
  export const Route = createFileRoute('/dashboard')({
8
192
  beforeLoad: async ({ context, location }) => {
193
+ // 인증 확인
9
194
  if (!context.auth.isAuthenticated) {
10
195
  throw redirect({ to: '/login', search: { redirect: location.href } })
11
196
  }
12
- return { userPermissions: await fetchPermissions(context.auth.user.id) }
197
+
198
+ // 라우트 특화 context 추가
199
+ const userPermissions = await fetchPermissions(context.auth.user!.id)
200
+ const dashboardConfig = await fetchConfig()
201
+
202
+ return {
203
+ userPermissions,
204
+ dashboardConfig,
205
+ }
206
+ },
207
+ loader: async ({ context }) => {
208
+ // beforeLoad의 context 사용
209
+ const data = await fetchDashboardData(context.userPermissions)
210
+ return { data }
13
211
  },
14
- loader: async ({ context }) => fetchDashboardData(context.userPermissions),
15
212
  component: DashboardPage,
16
213
  })
17
214
 
18
- // Protected Routes: _authed.tsx (pathless layout)
215
+ const DashboardPage = () => {
216
+ // context 접근
217
+ const { userPermissions, dashboardConfig } = Route.useRouteContext()
218
+
219
+ return (
220
+ <div>
221
+ {userPermissions.includes('edit') && <EditButton />}
222
+ </div>
223
+ )
224
+ }
225
+ ```
226
+
227
+ ### 부모 Context 확장 (누적)
228
+
229
+ context는 라우트 트리를 따라 merge됨. 각 라우트에서 추가한 context가 자식에 전파.
230
+
231
+ ```tsx
232
+ // /src/routes/_authed.tsx (parent)
19
233
  export const Route = createFileRoute('/_authed')({
20
- beforeLoad: async ({ location }) => {
234
+ beforeLoad: async ({ context }) => ({
235
+ user: await getCurrentUser(),
236
+ }),
237
+ component: () => <Outlet />,
238
+ })
239
+
240
+ // /src/routes/_authed/dashboard.tsx (child)
241
+ export const Route = createFileRoute('/_authed/dashboard')({
242
+ beforeLoad: async ({ context }) => ({
243
+ // 부모의 context + 추가 context
244
+ dashboardData: await fetchDashboard(context.user.id),
245
+ }),
246
+ component: Dashboard,
247
+ })
248
+
249
+ const Dashboard = () => {
250
+ // 부모 + 자신의 context 모두 접근
251
+ const { user, dashboardData } = Route.useRouteContext()
252
+
253
+ return <div>User: {user.name}, Data: {dashboardData}</div>
254
+ }
255
+ ```
256
+
257
+ ### 의존성 주입 패턴
258
+
259
+ ```tsx
260
+ // /src/routes/__root.tsx
261
+ export const Route = createRootRouteWithContext<{
262
+ fetchPosts: typeof fetchPosts
263
+ queryClient: QueryClient
264
+ }>()({
265
+ component: App,
266
+ })
267
+
268
+ // /src/routes/posts.tsx
269
+ export const Route = createFileRoute('/posts')({
270
+ loader: ({ context: { fetchPosts } }) => fetchPosts(),
271
+ })
272
+
273
+ // /src/routes/todos.tsx (TanStack Query와 함께)
274
+ export const Route = createFileRoute('/todos')({
275
+ loader: async ({ context }) => {
276
+ await context.queryClient.ensureQueryData({
277
+ queryKey: ['todos'],
278
+ queryFn: fetchTodos,
279
+ })
280
+ },
281
+ })
282
+ ```
283
+
284
+ </route_context>
285
+
286
+ ---
287
+
288
+ <pathless_layout>
289
+
290
+ ## Pathless Layout (_authed/): 인증 보호 그룹
291
+
292
+ _로 시작하는 레이아웃은 URL에 반영되지 않음. 인증, 권한 보호에 활용.
293
+
294
+ ### 구조
295
+
296
+ ```
297
+ routes/
298
+ ├── __root.tsx # Root layout
299
+ ├── _authed.tsx # Protected layout (경로 없음)
300
+ ├── _authed/
301
+ │ ├── dashboard.tsx # /dashboard (protected)
302
+ │ ├── settings.tsx # /settings (protected)
303
+ │ └── profile.tsx # /profile (protected)
304
+ ├── index.tsx # / (public)
305
+ ├── login.tsx # /login (public)
306
+ └── $.tsx # Catch-all
307
+ ```
308
+
309
+ ### 구현
310
+
311
+ ```tsx
312
+ // /src/routes/_authed.tsx
313
+ export const Route = createFileRoute('/_authed')({
314
+ beforeLoad: async ({ context, location }) => {
315
+ // 인증 확인
21
316
  const user = await getCurrentUser()
22
- if (!user) throw redirect({ to: '/login', search: { redirect: location.href } })
317
+
318
+ if (!user) {
319
+ throw redirect({
320
+ to: '/login',
321
+ search: { redirect: location.href }, // 로그인 후 돌아올 경로
322
+ })
323
+ }
324
+
325
+ // Context에 user 추가
23
326
  return { user }
24
327
  },
328
+ // Outlet으로 자식 라우트 렌더링
25
329
  component: () => <Outlet />,
26
330
  })
27
331
 
28
- // _authed/dashboard.tsx
332
+ // /src/routes/_authed/dashboard.tsx
29
333
  export const Route = createFileRoute('/_authed/dashboard')({
30
- component: DashboardPage,
334
+ component: Dashboard,
31
335
  })
32
- const DashboardPage = () => {
33
- const { user } = Route.useRouteContext() // _authed에서 전달된 context
336
+
337
+ const Dashboard = () => {
338
+ // 부모 _authed에서 전달된 user 접근
339
+ const { user } = Route.useRouteContext()
340
+
34
341
  return <h1>Welcome, {user.name}!</h1>
35
342
  }
36
343
 
37
- // Root Context
38
- interface RouterContext {
344
+ // /src/routes/_authed/settings.tsx
345
+ export const Route = createFileRoute('/_authed/settings')({
346
+ component: Settings,
347
+ })
348
+
349
+ const Settings = () => {
350
+ const { user } = Route.useRouteContext()
351
+
352
+ return (
353
+ <div>
354
+ <h1>Settings for {user.email}</h1>
355
+ </div>
356
+ )
357
+ }
358
+ ```
359
+
360
+ ### 공개 라우트
361
+
362
+ ```tsx
363
+ // /src/routes/index.tsx (public, 인증 불필요)
364
+ export const Route = createFileRoute('/')({
365
+ component: HomePage,
366
+ })
367
+
368
+ // /src/routes/login.tsx (public)
369
+ export const Route = createFileRoute('/login')({
370
+ component: LoginPage,
371
+ })
372
+
373
+ const LoginPage = () => {
374
+ const navigate = useNavigate()
375
+ const searchParams = useSearch({ strict: false })
376
+
377
+ const handleLogin = async (credentials) => {
378
+ await authenticate(credentials)
379
+
380
+ // 로그인 후 리다이렉트 (redirect 파라미터 사용)
381
+ navigate({
382
+ to: searchParams.redirect ?? '/dashboard',
383
+ })
384
+ }
385
+
386
+ return <LoginForm onSubmit={handleLogin} />
387
+ }
388
+ ```
389
+
390
+ </pathless_layout>
391
+
392
+ ---
393
+
394
+ <before_load>
395
+
396
+ ## beforeLoad: Context 추가 시점
397
+
398
+ beforeLoad는 loader 전에 실행. Context 추가에 최적.
399
+
400
+ ### 실행 순서
401
+
402
+ ```
403
+ 1. beforeLoad() 실행 (Serial, Top-Down)
404
+ |- context 접근 (Root + 부모 beforeLoad context)
405
+ |- location 접근
406
+ |- search 접근
407
+ +- context 추가 반환
408
+ |
409
+ 2. loader() 실행 (Parallel)
410
+ |- 추가된 context 사용
411
+ +- 데이터 반환
412
+ |
413
+ 3. component 렌더링
414
+ |- Route.useRouteContext() 사용
415
+ +- loader 데이터 사용
416
+ ```
417
+
418
+ ### beforeLoad 파라미터
419
+
420
+ ```typescript
421
+ interface BeforeLoadParams {
422
+ context: RouterContext // Root + 부모 context
423
+ location: Location // 현재 위치 정보
424
+ search: SearchParams // 검증된 search params
425
+ params: PathParams // 경로 파라미터
426
+ cause: 'enter' | 'stay' // 진입 원인
427
+ abortController: AbortController
428
+ }
429
+ ```
430
+
431
+ ### beforeLoad에서 인증 체크
432
+
433
+ ```tsx
434
+ export const Route = createFileRoute('/admin')({
435
+ beforeLoad: async ({ context, location }) => {
436
+ if (!context.auth.isAuthenticated) {
437
+ throw redirect({
438
+ to: '/login',
439
+ search: { redirect: location.href },
440
+ })
441
+ }
442
+
443
+ if (!context.auth.permissions.includes('admin')) {
444
+ throw new Error('Access denied')
445
+ }
446
+
447
+ // 추가 context
448
+ const adminData = await fetchAdminData()
449
+
450
+ return { adminData }
451
+ },
452
+ loader: async ({ context }) => {
453
+ // beforeLoad에서 추가된 adminData 사용
454
+ return fetchFullAdminPanel(context.adminData)
455
+ },
456
+ component: AdminPanel,
457
+ })
458
+ ```
459
+
460
+ ### 검색 쿼리 기반 Context
461
+
462
+ ```tsx
463
+ export const Route = createFileRoute('/posts')({
464
+ beforeLoad: async ({ search, context }) => {
465
+ // Search params 기반 context
466
+ const filters = {
467
+ category: search.category ?? 'all',
468
+ sort: search.sort ?? 'newest',
469
+ }
470
+
471
+ return { filters }
472
+ },
473
+ loader: async ({ context }) => {
474
+ // context.filters로 필터링
475
+ return fetchPosts(context.filters)
476
+ },
477
+ component: Posts,
478
+ })
479
+ ```
480
+
481
+ </before_load>
482
+
483
+ ---
484
+
485
+ <context_access>
486
+
487
+ ## Context 접근 방법
488
+
489
+ | 위치 | 접근 방법 | 접근 가능 |
490
+ |------|----------|---------|
491
+ | `beforeLoad()` | `{ context }` 파라미터 | Root context + 부모 beforeLoad context |
492
+ | `loader()` | `{ context }` 파라미터 | Root + Route context (beforeLoad 포함) |
493
+ | `component` | `Route.useRouteContext()` | Root + Route context |
494
+ | 다른 라우트 | `useRouterState()` | Root context만 |
495
+
496
+ ### Component에서 접근
497
+
498
+ ```tsx
499
+ const Page = () => {
500
+ // 현재 라우트 context (Root + Route-specific)
501
+ const context = Route.useRouteContext()
502
+
503
+ return <div>{context.user?.name}</div>
504
+ }
505
+ ```
506
+
507
+ ### Loader에서 접근
508
+
509
+ ```tsx
510
+ export const Route = createFileRoute('/posts')({
511
+ loader: async ({ context }) => {
512
+ // Root context + 부모 beforeLoad context
513
+ console.log(context.queryClient) // Root
514
+ console.log(context.user) // 부모 beforeLoad에서 추가됨
515
+ return fetchPosts()
516
+ },
517
+ component: Posts,
518
+ })
519
+ ```
520
+
521
+ ### 누적 Context로 Breadcrumb 생성
522
+
523
+ ```tsx
524
+ // /src/routes/__root.tsx
525
+ export const Route = createRootRoute({
526
+ component: () => {
527
+ const matches = useRouterState({ select: s => s.matches })
528
+
529
+ const breadcrumbs = matches
530
+ .filter(match => match.context.getTitle)
531
+ .map(({ pathname, context }) => ({
532
+ title: context.getTitle(),
533
+ path: pathname,
534
+ }))
535
+
536
+ return (
537
+ <div>
538
+ <nav>{breadcrumbs.map(b => (
539
+ <Link key={b.path} to={b.path}>{b.title}</Link>
540
+ ))}</nav>
541
+ <Outlet />
542
+ </div>
543
+ )
544
+ },
545
+ })
546
+ ```
547
+
548
+ ### 동적 타이틀 관리
549
+
550
+ ```tsx
551
+ export const Route = createRootRoute({
552
+ component: () => {
553
+ const matches = useRouterState({ select: s => s.matches })
554
+
555
+ const matchWithTitle = [...matches]
556
+ .reverse()
557
+ .find(d => d.context.getTitle)
558
+
559
+ const title = matchWithTitle?.context.getTitle() || 'My App'
560
+
561
+ return (
562
+ <html>
563
+ <head>
564
+ <title>{title}</title>
565
+ </head>
566
+ <body>
567
+ <Outlet />
568
+ </body>
569
+ </html>
570
+ )
571
+ },
572
+ })
573
+ ```
574
+
575
+ </context_access>
576
+
577
+ ---
578
+
579
+ <redirect>
580
+
581
+ ## redirect(): 조건부 리다이렉트
582
+
583
+ beforeLoad에서 throw하여 즉시 리다이렉트.
584
+
585
+ ### 기본 사용
586
+
587
+ ```tsx
588
+ import { redirect } from '@tanstack/react-router'
589
+
590
+ export const Route = createFileRoute('/dashboard')({
591
+ beforeLoad: async ({ context }) => {
592
+ if (!context.auth.isAuthenticated) {
593
+ throw redirect({ to: '/login' })
594
+ }
595
+ },
596
+ component: Dashboard,
597
+ })
598
+ ```
599
+
600
+ ### 리다이렉트 후 돌아오기
601
+
602
+ ```tsx
603
+ export const Route = createFileRoute('/_authed')({
604
+ beforeLoad: async ({ context, location }) => {
605
+ if (!context.auth.isAuthenticated) {
606
+ throw redirect({
607
+ to: '/login',
608
+ // Search params에 현재 경로 저장
609
+ search: { redirect: location.href },
610
+ })
611
+ }
612
+ },
613
+ component: () => <Outlet />,
614
+ })
615
+
616
+ // /src/routes/login.tsx
617
+ export const Route = createFileRoute('/login')({
618
+ component: LoginPage,
619
+ })
620
+
621
+ const LoginPage = () => {
622
+ const navigate = useNavigate()
623
+ const { redirect: redirectPath } = useSearch()
624
+
625
+ const handleLogin = async (credentials) => {
626
+ await authenticate(credentials)
627
+
628
+ // 원래 경로로 리다이렉트
629
+ navigate({ to: redirectPath ?? '/dashboard' })
630
+ }
631
+
632
+ return <LoginForm onSubmit={handleLogin} />
633
+ }
634
+ ```
635
+
636
+ ### Params와 Search 함께 사용
637
+
638
+ ```tsx
639
+ throw redirect({
640
+ to: '/posts/$postId',
641
+ params: { postId: '123' },
642
+ search: { tab: 'comments' },
643
+ })
644
+ ```
645
+
646
+ ### History Replace
647
+
648
+ ```tsx
649
+ // 브라우저 히스토리에 남지 않음 (뒤로 가기 시 이전 페이지로)
650
+ throw redirect({
651
+ to: '/login',
652
+ replace: true,
653
+ })
654
+ ```
655
+
656
+ ### 조건부 리다이렉트
657
+
658
+ ```tsx
659
+ export const Route = createFileRoute('/posts/$postId')({
660
+ beforeLoad: async ({ params, context }) => {
661
+ // 삭제된 포스트: 목록으로
662
+ const post = await getPost(params.postId)
663
+ if (post.isDeleted) {
664
+ throw redirect({ to: '/posts' })
665
+ }
666
+
667
+ // 비공개 포스트 + 소유자 아님: 홈으로
668
+ if (!post.isPublished && post.ownerId !== context.auth.user?.id) {
669
+ throw redirect({ to: '/' })
670
+ }
671
+
672
+ return { post }
673
+ },
674
+ component: PostDetail,
675
+ })
676
+ ```
677
+
678
+ </redirect>
679
+
680
+ ---
681
+
682
+ <auth_example>
683
+
684
+ ## 실전 예시: Better Auth 통합
685
+
686
+ Better Auth와 Route Context 통합.
687
+
688
+ ### Context 타입
689
+
690
+ ```tsx
691
+ // /src/lib/router-context.ts
692
+ import { QueryClient } from '@tanstack/react-query'
693
+ import { User, Session } from 'better-auth'
694
+
695
+ export interface RouterContext {
39
696
  queryClient: QueryClient
40
- auth: { isAuthenticated: boolean; user: User | null }
697
+ auth: {
698
+ isAuthenticated: boolean
699
+ user: User | null
700
+ session: Session | null
701
+ }
41
702
  }
703
+ ```
704
+
705
+ ### Root Route
706
+
707
+ ```tsx
708
+ // /src/routes/__root.tsx
709
+ import { createRootRouteWithContext } from '@tanstack/react-router'
710
+ import { RouterContext } from '@/lib/router-context'
42
711
 
43
712
  export const Route = createRootRouteWithContext<RouterContext>()({
44
713
  component: RootLayout,
45
714
  })
46
715
 
716
+ const RootLayout = () => (
717
+ <div>
718
+ <Header />
719
+ <Outlet />
720
+ </div>
721
+ )
722
+
723
+ const Header = () => {
724
+ const { user } = Route.useRouteContext()
725
+
726
+ return (
727
+ <header>
728
+ {user && <span>Logged in as {user.email}</span>}
729
+ </header>
730
+ )
731
+ }
732
+ ```
733
+
734
+ ### Protected Layout
735
+
736
+ ```tsx
737
+ // /src/routes/_authed.tsx
738
+ import { redirect } from '@tanstack/react-router'
739
+ import { authClient } from '@/lib/auth-client'
740
+
741
+ export const Route = createFileRoute('/_authed')({
742
+ beforeLoad: async ({ context, location }) => {
743
+ // Better Auth로 세션 확인
744
+ const session = await authClient.getSession()
745
+
746
+ if (!session) {
747
+ throw redirect({
748
+ to: '/login',
749
+ search: { redirect: location.href },
750
+ })
751
+ }
752
+
753
+ // Context 업데이트
754
+ context.auth = {
755
+ isAuthenticated: true,
756
+ user: session.user,
757
+ session,
758
+ }
759
+
760
+ return {}
761
+ },
762
+ component: () => <Outlet />,
763
+ })
764
+ ```
765
+
766
+ ### Protected Route
767
+
768
+ ```tsx
769
+ // /src/routes/_authed/dashboard.tsx
770
+ export const Route = createFileRoute('/_authed/dashboard')({
771
+ component: Dashboard,
772
+ })
773
+
774
+ const Dashboard = () => {
775
+ const { user } = Route.useRouteContext()
776
+
777
+ return (
778
+ <div>
779
+ <h1>Welcome, {user.name || user.email}!</h1>
780
+ <Link to="/logout">Logout</Link>
781
+ </div>
782
+ )
783
+ }
784
+ ```
785
+
786
+ ### Router 초기화
787
+
788
+ ```tsx
789
+ // /src/main.tsx
790
+ import { QueryClient } from '@tanstack/react-query'
791
+ import { createRouter } from '@tanstack/react-router'
792
+ import { authClient } from '@/lib/auth-client'
793
+ import { routeTree } from './routeTree.gen'
794
+ import { RouterContext } from '@/lib/router-context'
795
+
796
+ const queryClient = new QueryClient()
797
+
798
+ // 초기 컨텍스트
799
+ const initialContext: RouterContext = {
800
+ queryClient,
801
+ auth: {
802
+ isAuthenticated: false,
803
+ user: null,
804
+ session: null,
805
+ }
806
+ }
807
+
47
808
  const router = createRouter({
48
809
  routeTree,
49
- context: { queryClient, auth: { isAuthenticated: false, user: null } },
810
+ context: initialContext,
50
811
  })
51
812
 
52
- // redirect
53
- throw redirect({ to: '/login' })
54
- throw redirect({ to: '/login', search: { redirect: '/dashboard' } })
55
- throw redirect({ to: '/posts/$postId', params: { postId: '123' } })
56
- throw redirect({ to: '/home', replace: true })
813
+ // 세션 로드
814
+ const session = await authClient.getSession()
815
+ if (session) {
816
+ router.context = {
817
+ ...router.context,
818
+ auth: {
819
+ isAuthenticated: true,
820
+ user: session.user,
821
+ session,
822
+ }
823
+ }
824
+ }
825
+
826
+ createRoot(document.getElementById('root')).render(
827
+ <RouterProvider router={router} />
828
+ )
829
+ ```
830
+
831
+ </auth_example>
832
+
833
+ ---
834
+
835
+ <router_invalidate>
836
+
837
+ ## router.invalidate(): Context 재설정
838
+
839
+ 인증 상태 변경 시 라우터 상태 갱신. 모든 활성 라우트의 loader를 즉시 재실행하고 캐시를 stale로 표시.
840
+
841
+ ### 로그인 후
842
+
843
+ ```tsx
844
+ const LoginPage = () => {
845
+ const router = useRouter()
846
+ const navigate = useNavigate()
847
+
848
+ const handleLogin = async (credentials) => {
849
+ await authenticate(credentials)
850
+
851
+ // 라우터 context 갱신 (loader와 beforeLoad 재실행)
852
+ await router.invalidate()
853
+
854
+ // 대시보드로 이동
855
+ navigate({ to: '/dashboard' })
856
+ }
857
+
858
+ return <LoginForm onSubmit={handleLogin} />
859
+ }
57
860
  ```
58
861
 
59
- </patterns>
862
+ ### 로그아웃 후
863
+
864
+ ```tsx
865
+ const LogoutButton = () => {
866
+ const router = useRouter()
867
+ const navigate = useNavigate()
60
868
 
61
- <structure>
869
+ const handleLogout = async () => {
870
+ await logout()
62
871
 
872
+ // Context 갱신
873
+ router.context = {
874
+ ...router.context,
875
+ auth: {
876
+ isAuthenticated: false,
877
+ user: null,
878
+ session: null,
879
+ }
880
+ }
881
+
882
+ // 라우터 상태 갱신
883
+ await router.invalidate()
884
+
885
+ // 홈으로 이동
886
+ navigate({ to: '/' })
887
+ }
888
+
889
+ return <button onClick={handleLogout}>Logout</button>
890
+ }
63
891
  ```
64
- routes/
65
- ├── _authed.tsx # Protected layout
66
- ├── _authed/
67
- │ ├── dashboard.tsx # /dashboard (protected)
68
- │ ├── settings.tsx # /settings (protected)
69
- │ └── profile.tsx # /profile (protected)
70
- ├── login.tsx # /login (public)
71
- └── index.tsx # / (public)
892
+
893
+ ### 인증 상태 변경 감지
894
+
895
+ ```tsx
896
+ function useAuth() {
897
+ const router = useRouter()
898
+ const [user, setUser] = useState<User | null>(null)
899
+
900
+ useEffect(() => {
901
+ const unsubscribe = auth.onAuthStateChanged(user => {
902
+ setUser(user)
903
+ router.invalidate() // 인증 변경 시 라우터 갱신
904
+ })
905
+
906
+ return unsubscribe
907
+ }, [])
908
+
909
+ return user
910
+ }
72
911
  ```
73
912
 
74
- </structure>
913
+ </router_invalidate>
914
+
915
+ ---
916
+
917
+ <dos_donts>
75
918
 
76
- <usage>
919
+ ## Do's & Don'ts
77
920
 
78
- | 위치 | 접근 방법 |
79
- |------|----------|
80
- | beforeLoad | `{ context }` 파라미터 |
81
- | loader | `{ context }` 파라미터 |
82
- | component | `Route.useRouteContext()` |
921
+ | Do | Don't |
922
+ |-------|---------|
923
+ | `createRootRouteWithContext<T>()` | context 없이 root 생성 |
924
+ | `beforeLoad`에서 인증 체크 | `component`에서 try-catch |
925
+ | `_authed/` layout으로 그룹 보호 | 모든 라우트에 인증 로직 중복 |
926
+ | `redirect()` throw | `navigate()`로 리다이렉트 |
927
+ | `beforeLoad`에서 context 추가 | `loader`에서 context 추가 |
928
+ | `Route.useRouteContext()` | 글로벌 상태로 context 관리 |
929
+ | Root context 최소화 | Root에 모든 상태 넣기 |
930
+ | Route-specific context 사용 | 모든 context Root에 정의 |
931
+ | RouterProvider context로 React 훅 주입 | beforeLoad에서 React 훅 호출 |
932
+ | `router.invalidate()` 인증 변경 시 | 수동으로 context 동기화 |
933
+ | `()()` 이중 호출 (팩토리 패턴) | `createRootRouteWithContext<T>()` 단일 호출 |
83
934
 
84
- </usage>
935
+ </dos_donts>