@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,89 +1,828 @@
1
1
  # TanStack Router - Error Handling
2
2
 
3
- <patterns>
3
+ > TanStack Router v1.159.4
4
+
5
+ 에러 경계, 404 처리, 로딩 상태를 다룬다.
6
+
7
+ ---
8
+
9
+ <error_component>
10
+
11
+ ## errorComponent: 일반 에러 처리
12
+
13
+ Loader나 beforeLoad에서 throw한 Error를 잡는다.
14
+
15
+ ### 기본 사용
4
16
 
5
17
  ```tsx
6
- // errorComponent
18
+ import { ErrorComponentProps } from '@tanstack/react-router'
19
+
7
20
  export const Route = createFileRoute('/posts/$postId')({
8
21
  loader: async ({ params }) => {
9
22
  const post = await getPost(params.postId)
10
- if (!post) throw new Error('Post not found')
23
+ if (!post) {
24
+ // 임의의 Error throw
25
+ throw new Error(`Post ${params.postId} not found`)
26
+ }
11
27
  return { post }
12
28
  },
13
29
  errorComponent: PostError,
14
- component: PostPage,
30
+ component: PostDetail,
15
31
  })
16
32
 
17
33
  const PostError = ({ error, reset }: ErrorComponentProps) => (
18
- <div>
34
+ <div className="error-container">
19
35
  <h2>Error loading post</h2>
20
36
  <p>{error.message}</p>
21
37
  <button onClick={reset}>Retry</button>
22
38
  </div>
23
39
  )
24
40
 
25
- // notFoundComponent
41
+ const PostDetail = () => {
42
+ const { post } = Route.useLoaderData()
43
+ return <h1>{post.title}</h1>
44
+ }
45
+ ```
46
+
47
+ ### ErrorComponentProps
48
+
49
+ ```tsx
50
+ interface ErrorComponentProps {
51
+ error: Error
52
+ reset: () => void // Retry 버튼: loader와 beforeLoad 재실행
53
+ info?: string
54
+ }
55
+ ```
56
+
57
+ ### 비동기 에러
58
+
59
+ ```tsx
60
+ export const Route = createFileRoute('/users')({
61
+ loader: async () => {
62
+ try {
63
+ return await fetchUsers()
64
+ } catch (error) {
65
+ // 에러를 명시적으로 throw
66
+ throw new Error(`Failed to fetch users: ${error.message}`)
67
+ }
68
+ },
69
+ errorComponent: ({ error, reset }) => (
70
+ <div>
71
+ <p>{error.message}</p>
72
+ <button onClick={reset}>Try again</button>
73
+ </div>
74
+ ),
75
+ component: UsersList,
76
+ })
77
+ ```
78
+
79
+ ### 에러 타입 구분
80
+
81
+ ```tsx
82
+ const CustomError = ({ error, reset }: ErrorComponentProps) => {
83
+ // Network error
84
+ if (error instanceof TypeError && error.message.includes('fetch')) {
85
+ return (
86
+ <div>
87
+ <p>Network connection error. Please check your internet.</p>
88
+ <button onClick={reset}>Retry</button>
89
+ </div>
90
+ )
91
+ }
92
+
93
+ // 인증 에러
94
+ if (error.message.includes('unauthorized') || error.message.includes('401')) {
95
+ return <Navigate to="/login" />
96
+ }
97
+
98
+ // Validation 에러
99
+ if (error.message.includes('validation')) {
100
+ return <div>Invalid data provided. Please try again.</div>
101
+ }
102
+
103
+ // 기타 에러
104
+ return (
105
+ <div>
106
+ <p>Something went wrong</p>
107
+ <details>
108
+ <summary>Details</summary>
109
+ <pre>{error.message}</pre>
110
+ </details>
111
+ <button onClick={reset}>Retry</button>
112
+ </div>
113
+ )
114
+ }
115
+
116
+ export const Route = createFileRoute('/dashboard')({
117
+ loader: async () => {
118
+ // ...
119
+ },
120
+ errorComponent: CustomError,
121
+ component: Dashboard,
122
+ })
123
+ ```
124
+
125
+ ### DefaultErrorComponent로 폴백
126
+
127
+ 커스텀 에러에서 처리 못하는 에러는 기본 컴포넌트로 위임.
128
+
129
+ ```tsx
130
+ import { ErrorComponent } from '@tanstack/react-router'
131
+
132
+ export const Route = createFileRoute('/posts')({
133
+ loader: () => fetchPosts(),
134
+ errorComponent: ({ error }) => {
135
+ if (error instanceof MyCustomError) {
136
+ return <div>{error.message}</div>
137
+ }
138
+
139
+ // 기본 ErrorComponent로 폴백 (항상 추천)
140
+ return <ErrorComponent error={error} />
141
+ },
142
+ })
143
+ ```
144
+
145
+ ### 상위로 에러 전파
146
+
147
+ errorComponent가 없으면 부모 라우트로 전파.
148
+
149
+ ```tsx
150
+ // /src/routes/posts/$postId.tsx (errorComponent 없음)
151
+ export const Route = createFileRoute('/posts/$postId')({
152
+ loader: async ({ params }) => {
153
+ throw new Error('Failed to load post')
154
+ },
155
+ component: PostDetail,
156
+ })
157
+
158
+ // /src/routes/posts.tsx (errorComponent 있음)
159
+ export const Route = createFileRoute('/posts')({
160
+ errorComponent: PostsError, // /posts/$postId의 에러도 여기서 잡힘
161
+ component: () => <Outlet />,
162
+ })
163
+ ```
164
+
165
+ ### onError / onCatch 콜백
166
+
167
+ ```tsx
168
+ export const Route = createFileRoute('/posts')({
169
+ loader: () => fetchPosts(),
170
+ // 에러 로깅
171
+ onError: ({ error }) => {
172
+ console.error('Loader error:', error)
173
+ },
174
+ // CatchBoundary에서 잡힌 에러
175
+ onCatch: ({ error, errorInfo }) => {
176
+ console.error('Caught error:', error)
177
+ },
178
+ errorComponent: PostsError,
179
+ component: PostsList,
180
+ })
181
+ ```
182
+
183
+ </error_component>
184
+
185
+ ---
186
+
187
+ <not_found_component>
188
+
189
+ ## notFoundComponent: 404 처리
190
+
191
+ notFound() 함수로 throw하면 notFoundComponent 표시.
192
+
193
+ ### notFoundMode 옵션
194
+
195
+ | 모드 | 설명 |
196
+ |------|------|
197
+ | `fuzzy` (기본) | 가장 가까운 부모 라우트의 notFoundComponent 사용. 부모 레이아웃 최대 보존. |
198
+ | `root` | 모든 404를 root의 notFoundComponent에서 처리 |
199
+
200
+ ```tsx
201
+ const router = createRouter({
202
+ routeTree,
203
+ notFoundMode: 'fuzzy', // 기본값
204
+ })
205
+ ```
206
+
207
+ ### fuzzy 모드 동작 원리
208
+
209
+ `/posts/1/edit` 접근 시 (edit 라우트가 없는 경우):
210
+
211
+ - `__root__` (notFoundComponent 있음)
212
+ - `posts` (notFoundComponent 있음) -> 여기서 처리 (가장 가까운 부모)
213
+ - `$postId`
214
+
215
+ 가장 가까운 적합한 부모 조건:
216
+ 1. 자식 라우트를 가짐 (Outlet이 있음)
217
+ 2. notFoundComponent가 설정됨
218
+
219
+ ### 기본 사용
220
+
221
+ ```tsx
222
+ import { notFound } from '@tanstack/react-router'
223
+
26
224
  export const Route = createFileRoute('/posts/$postId')({
27
225
  loader: async ({ params }) => {
28
226
  const post = await getPost(params.postId)
29
- if (!post) throw notFound({ data: { searchedId: params.postId } })
227
+
228
+ // Post 없으면 404
229
+ if (!post) {
230
+ throw notFound({
231
+ data: { searchedId: params.postId }
232
+ })
233
+ }
234
+
30
235
  return { post }
31
236
  },
32
- notFoundComponent: ({ data }) => <p>Post {data?.searchedId} not found</p>,
33
- component: PostPage,
237
+ notFoundComponent: PostNotFound,
238
+ component: PostDetail,
34
239
  })
35
240
 
36
- // Root 404
241
+ const PostNotFound = ({ data }: { data?: { searchedId: string } }) => (
242
+ <div>
243
+ <h2>Post not found</h2>
244
+ <p>Could not find post {data?.searchedId}</p>
245
+ <Link to="/posts">Back to posts</Link>
246
+ </div>
247
+ )
248
+
249
+ const PostDetail = () => {
250
+ const { post } = Route.useLoaderData()
251
+ return <h1>{post.title}</h1>
252
+ }
253
+ ```
254
+
255
+ ### Root 404 (전역 핸들링)
256
+
257
+ ```tsx
258
+ // /src/routes/__root.tsx
37
259
  export const Route = createRootRoute({
38
260
  component: RootLayout,
39
- notFoundComponent: () => (
261
+ notFoundComponent: NotFound, // 모든 404 처리
262
+ })
263
+
264
+ const NotFound = () => (
265
+ <div>
266
+ <h1>404 - Page Not Found</h1>
267
+ <Link to="/">Go Home</Link>
268
+ </div>
269
+ )
270
+
271
+ const RootLayout = () => (
272
+ <div>
273
+ <nav>{/* ... */}</nav>
274
+ <Outlet />
275
+ </div>
276
+ )
277
+ ```
278
+
279
+ ### defaultNotFoundComponent (라우터 수준)
280
+
281
+ 모든 라우트에 기본 404 컴포넌트 적용.
282
+
283
+ ```tsx
284
+ const router = createRouter({
285
+ routeTree,
286
+ defaultNotFoundComponent: () => (
40
287
  <div>
41
- <h1>404</h1>
42
- <Link to="/">Go Home</Link>
288
+ <p>Not found!</p>
289
+ <Link to="/">Go home</Link>
43
290
  </div>
44
291
  ),
45
292
  })
293
+ ```
46
294
 
47
- // pendingComponent
295
+ ### 특정 라우트로 404 전파 (routeId)
296
+
297
+ ```tsx
298
+ import { rootRouteId } from '@tanstack/react-router'
299
+
300
+ // 특정 부모 라우트의 notFoundComponent 사용
301
+ export const Route = createFileRoute('/_pathless/route-a')({
302
+ loader: async () => {
303
+ throw notFound({ routeId: '/_pathlessLayout' })
304
+ // ^^^^^^^^^ 자동완성 지원
305
+ },
306
+ // 이 notFoundComponent는 렌더링되지 않음
307
+ notFoundComponent: () => <p>여기는 안 보임</p>,
308
+ })
309
+
310
+ // root 라우트로 직접 전파
311
+ export const Route = createFileRoute('/posts/$postId')({
312
+ loader: async ({ params: { postId } }) => {
313
+ const post = await getPost(postId)
314
+ if (!post) throw notFound({ routeId: rootRouteId })
315
+ return { post }
316
+ },
317
+ })
318
+ ```
319
+
320
+ ### notFound() 사용 패턴
321
+
322
+ ```tsx
323
+ // 단순 throw
324
+ throw notFound()
325
+
326
+ // 데이터 전달
327
+ throw notFound({
328
+ data: {
329
+ resourceType: 'Post',
330
+ searchedId: params.postId,
331
+ }
332
+ })
333
+
334
+ // 특정 라우트 지정
335
+ throw notFound({ routeId: '/_pathlessLayout' })
336
+
337
+ // throw 옵션
338
+ notFound({ throw: true }) // throw 대신 함수 내에서 throw
339
+
340
+ // beforeLoad에서도 사용 가능 (항상 __root notFoundComponent 사용)
341
+ export const Route = createFileRoute('/admin')({
342
+ beforeLoad: async ({ context }) => {
343
+ if (!context.user?.isAdmin) {
344
+ throw notFound()
345
+ }
346
+ },
347
+ component: AdminPanel,
348
+ })
349
+ ```
350
+
351
+ > 주의: beforeLoad에서 notFound()를 throw하면 항상 `__root`의 notFoundComponent가 사용됨. 이는 beforeLoad가 loader 전에 실행되므로 레이아웃 데이터 로딩이 보장되지 않기 때문.
352
+
353
+ ### notFound() vs Error throw
354
+
355
+ ```tsx
356
+ export const Route = createFileRoute('/posts/$postId')({
357
+ loader: async ({ params }) => {
358
+ const post = await getPost(params.postId)
359
+
360
+ if (!post) {
361
+ // 리소스 없음: notFound() 사용
362
+ throw notFound()
363
+ }
364
+
365
+ // API 에러로 실패
366
+ if (!post.data) {
367
+ // 예상치 못한 에러: Error throw
368
+ throw new Error('Invalid post data')
369
+ }
370
+
371
+ return { post }
372
+ },
373
+ notFoundComponent: () => <div>Post not found</div>,
374
+ errorComponent: ({ error }) => <div>{error.message}</div>,
375
+ component: PostDetail,
376
+ })
377
+ ```
378
+
379
+ </not_found_component>
380
+
381
+ ---
382
+
383
+ <catch_not_found>
384
+
385
+ ## CatchNotFound: 컴포넌트 레벨 404
386
+
387
+ 컴포넌트 내부에서 notFound() throw 가능.
388
+
389
+ ```tsx
390
+ import { CatchNotFound } from '@tanstack/react-router'
391
+
392
+ export const Route = createFileRoute('/products/$productId')({
393
+ component: () => (
394
+ <CatchNotFound fallback={<div>Product not found</div>}>
395
+ <ProductDetail />
396
+ </CatchNotFound>
397
+ ),
398
+ })
399
+
400
+ const ProductDetail = () => {
401
+ const { productId } = Route.useParams()
402
+ const product = useQuery({
403
+ queryKey: ['products', productId],
404
+ queryFn: async () => {
405
+ const res = await getProduct(productId)
406
+ if (!res) throw notFound() // 컴포넌트에서 throw
407
+ return res
408
+ },
409
+ })
410
+
411
+ if (product.isPending) return <Spinner />
412
+
413
+ return <h1>{product.data.name}</h1>
414
+ }
415
+ ```
416
+
417
+ > 권장: 가능하면 loader에서 notFound()를 throw. 컴포넌트에서 throw하면 loader 데이터 타입이 불안정하고 UI 깜빡임이 발생할 수 있음.
418
+
419
+ </catch_not_found>
420
+
421
+ ---
422
+
423
+ <pending_component>
424
+
425
+ ## pendingComponent: 로딩 상태
426
+
427
+ Loader 실행 중일 때 표시. **기본 1000ms** 이후에 노출.
428
+
429
+ ### 기본 사용
430
+
431
+ ```tsx
48
432
  export const Route = createFileRoute('/posts')({
49
- loader: async () => fetchPosts(),
433
+ loader: async () => {
434
+ // 로딩 시뮬레이션 (1초)
435
+ await new Promise(r => setTimeout(r, 1000))
436
+ return fetchPosts()
437
+ },
438
+ // 1000ms 후 표시
50
439
  pendingComponent: () => <Spinner />,
51
- pendingMs: 200, // 200ms 후 표시
52
- pendingMinMs: 500, // 최소 500ms 유지 (깜빡임 방지)
53
- component: PostsPage,
440
+ component: PostsList,
54
441
  })
442
+ ```
443
+
444
+ ### pendingMs & pendingMinMs
445
+
446
+ ```tsx
447
+ export const Route = createFileRoute('/dashboard')({
448
+ loader: async () => fetchDashboard(),
449
+
450
+ // 로딩이 200ms 이상 걸리면 표시
451
+ pendingMs: 200,
452
+
453
+ // 최소 500ms 표시 (깜빡임 방지)
454
+ pendingMinMs: 500,
455
+
456
+ pendingComponent: () => <LoadingScreen />,
457
+ component: Dashboard,
458
+ })
459
+ ```
460
+
461
+ ### 라우터 수준 기본값
462
+
463
+ ```tsx
464
+ const router = createRouter({
465
+ routeTree,
466
+ defaultPendingMs: 200, // 기본 대기 시간
467
+ defaultPendingMinMs: 300, // 기본 최소 표시 시간
468
+ })
469
+ ```
470
+
471
+ ### 커스텀 로딩 UI
472
+
473
+ ```tsx
474
+ const LoadingSpinner = () => (
475
+ <div className="flex items-center justify-center">
476
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900" />
477
+ <span>Loading...</span>
478
+ </div>
479
+ )
480
+
481
+ export const Route = createFileRoute('/blog')({
482
+ loader: async () => fetchBlogPosts(),
483
+ pendingComponent: LoadingSpinner,
484
+ pendingMs: 100,
485
+ pendingMinMs: 300,
486
+ component: Blog,
487
+ })
488
+ ```
489
+
490
+ ### 스켈레톤 화면
491
+
492
+ ```tsx
493
+ const PostSkeleton = () => (
494
+ <div className="space-y-4">
495
+ {[1, 2, 3].map(i => (
496
+ <div key={i} className="h-20 bg-gray-200 rounded animate-pulse" />
497
+ ))}
498
+ </div>
499
+ )
500
+
501
+ export const Route = createFileRoute('/posts')({
502
+ loader: async () => fetchPosts(),
503
+ pendingComponent: PostSkeleton,
504
+ component: PostsList,
505
+ })
506
+ ```
55
507
 
56
- // Catch-all (routes/$.tsx)
508
+ </pending_component>
509
+
510
+ ---
511
+
512
+ <catch_all>
513
+
514
+ ## Catch-all Route: 경로 매칭 실패
515
+
516
+ routes/$.tsx로 존재하지 않는 모든 경로 캐치.
517
+
518
+ ### 기본 설정
519
+
520
+ ```tsx
521
+ // /src/routes/$.tsx
522
+ export const Route = createFileRoute('/$')({
523
+ component: NotFoundPage,
524
+ })
525
+
526
+ const NotFoundPage = () => {
527
+ const { _splat } = Route.useParams()
528
+
529
+ return (
530
+ <div className="text-center">
531
+ <h1>404 - Not Found</h1>
532
+ <p>The page /{_splat} does not exist.</p>
533
+ <Link to="/">Go Home</Link>
534
+ </div>
535
+ )
536
+ }
537
+ ```
538
+
539
+ ### _splat 파라미터
540
+
541
+ _splat은 매칭되지 않은 나머지 경로 문자열.
542
+
543
+ ```tsx
544
+ // /users/123/settings 요청
545
+ // _splat = 'users/123/settings'
546
+
547
+ const DetailedNotFound = () => {
548
+ const { _splat } = Route.useParams()
549
+ const segments = _splat?.split('/') ?? []
550
+
551
+ return (
552
+ <div>
553
+ <h1>Page Not Found</h1>
554
+ <p>Requested path: /{_splat}</p>
555
+ <p>Segments: {segments.join(', ')}</p>
556
+ <Link to="/">Home</Link>
557
+ </div>
558
+ )
559
+ }
560
+ ```
561
+
562
+ ### errorComponent와 함께 사용
563
+
564
+ ```tsx
57
565
  export const Route = createFileRoute('/$')({
58
- component: () => {
59
- const { _splat } = Route.useParams()
60
- return <div>Page Not Found: /{_splat}</div>
566
+ errorComponent: CatchAllError,
567
+ component: CatchAllNotFound,
568
+ })
569
+
570
+ const CatchAllError = ({ error }: ErrorComponentProps) => (
571
+ <div>
572
+ <h1>Error</h1>
573
+ <p>{error.message}</p>
574
+ </div>
575
+ )
576
+
577
+ const CatchAllNotFound = () => {
578
+ const { _splat } = Route.useParams()
579
+ return <div>Page /{_splat} not found</div>
580
+ }
581
+ ```
582
+
583
+ </catch_all>
584
+
585
+ ---
586
+
587
+ <priority>
588
+
589
+ ## 에러 처리 우선순위
590
+
591
+ 여러 상태가 동시에 발생할 때 표시 순서.
592
+
593
+ | 우선순위 | 컴포넌트 | 조건 | 표시 시기 |
594
+ |---------|---------|------|----------|
595
+ | 1 | `errorComponent` | loader/beforeLoad에서 Error throw | 에러 즉시 |
596
+ | 2 | `notFoundComponent` | `notFound()` throw | 404 즉시 |
597
+ | 3 | `pendingComponent` | loader 실행 중 (pendingMs 이후) | 로딩 진행 중 |
598
+ | 4 | `component` | 정상 데이터 | 완료 |
599
+
600
+ ### 라우트 로딩 라이프사이클
601
+
602
+ ```
603
+ Route Matching (Top-Down)
604
+ route.params.parse
605
+ route.validateSearch
606
+ |
607
+ Route Pre-Loading (Serial)
608
+ route.beforeLoad
609
+ route.onError -> errorComponent
610
+ |
611
+ Route Loading (Parallel)
612
+ route.component.preload?
613
+ route.loader
614
+ -> pendingComponent (pendingMs 초과 시)
615
+ -> component (완료)
616
+ route.onError -> errorComponent
617
+ ```
618
+
619
+ ### 부모로 전파
620
+
621
+ ```tsx
622
+ // 자식에 errorComponent 없으면 부모로 전파
623
+ // /src/routes/posts/$postId.tsx (errorComponent 없음)
624
+ export const Route = createFileRoute('/posts/$postId')({
625
+ loader: async () => {
626
+ throw new Error('Failed!')
61
627
  },
628
+ component: PostDetail,
62
629
  })
63
630
 
64
- // 에러 타입 구분
65
- const CustomError = ({ error, reset }: ErrorComponentProps) => {
66
- if (error instanceof TypeError && error.message.includes('fetch')) {
67
- return <div><p>Network error</p><button onClick={reset}>Retry</button></div>
631
+ // /src/routes/posts.tsx (errorComponent 있음)
632
+ export const Route = createFileRoute('/posts')({
633
+ errorComponent: PostsError, // <- 여기서 잡힘
634
+ component: () => <Outlet />,
635
+ })
636
+ ```
637
+
638
+ </priority>
639
+
640
+ ---
641
+
642
+ <error_recovery>
643
+
644
+ ## 에러 복구 (router.invalidate)
645
+
646
+ 에러 후 상태 재설정. 인증 갱신, 데이터 재로드 등에 활용.
647
+
648
+ ### reset() vs router.invalidate()
649
+
650
+ | 방법 | 용도 |
651
+ |------|------|
652
+ | `reset()` | 에러 바운더리 UI만 리셋 (컴포넌트 렌더링 에러) |
653
+ | `router.invalidate()` | loader 재실행 + 에러 바운더리 리셋 (데이터 로딩 에러) |
654
+
655
+ ```tsx
656
+ // 데이터 로딩 에러: router.invalidate() 사용 (권장)
657
+ const ErrorWithRecovery = ({ error, reset }: ErrorComponentProps) => {
658
+ const router = useRouter()
659
+
660
+ return (
661
+ <div>
662
+ <p>{error.message}</p>
663
+ <button onClick={() => router.invalidate()}>
664
+ Retry & Refresh
665
+ </button>
666
+ </div>
667
+ )
668
+ }
669
+
670
+ // React Query와 함께 사용
671
+ const ErrorWithQueryRecovery = ({ error, reset }: ErrorComponentProps) => {
672
+ const router = useRouter()
673
+ const queryClient = useQueryClient()
674
+
675
+ const handleRetry = () => {
676
+ // 1. React Query 캐시 정리
677
+ queryClient.clear()
678
+
679
+ // 2. 라우터 상태 갱신 (loader 재실행 + 에러 바운더리 리셋)
680
+ router.invalidate()
681
+ }
682
+
683
+ return (
684
+ <div>
685
+ <p>{error.message}</p>
686
+ <button onClick={handleRetry}>Retry & Refresh</button>
687
+ </div>
688
+ )
689
+ }
690
+ ```
691
+
692
+ ### 인증 에러 후 로그인으로 리다이렉트
693
+
694
+ ```tsx
695
+ const AuthError = ({ error, reset }: ErrorComponentProps) => {
696
+ const router = useRouter()
697
+ const navigate = useNavigate()
698
+
699
+ const handleLogin = () => {
700
+ // 인증 상태 갱신
701
+ router.invalidate()
702
+
703
+ // 로그인 페이지로 리다이렉트
704
+ navigate({
705
+ to: '/login',
706
+ search: { redirect: location.href },
707
+ })
68
708
  }
709
+
69
710
  if (error.message.includes('unauthorized')) {
70
- return <Navigate to="/login" />
711
+ return (
712
+ <div>
713
+ <p>Session expired. Please log in again.</p>
714
+ <button onClick={handleLogin}>Go to Login</button>
715
+ </div>
716
+ )
71
717
  }
72
- return <div><p>Something went wrong</p><button onClick={reset}>Retry</button></div>
718
+
719
+ return <div>{error.message}</div>
73
720
  }
74
721
  ```
75
722
 
76
- </patterns>
723
+ </error_recovery>
77
724
 
78
- <priority>
725
+ ---
79
726
 
80
- | 우선순위 | 컴포넌트 | 조건 |
81
- |---------|---------|------|
82
- | 1 | `errorComponent` | loader/beforeLoad에서 Error throw |
83
- | 2 | `notFoundComponent` | `notFound()` throw |
84
- | 3 | `pendingComponent` | loader 실행 중 (pendingMs 이후) |
85
- | 4 | `component` | 정상 렌더링 |
727
+ <default_error>
86
728
 
87
- 에러 전파: 하위 → 상위 (errorComponent 없으면 부모로 전파)
729
+ ## Default ErrorComponent
88
730
 
89
- </priority>
731
+ 기본 에러 컴포넌트 import. 빠른 프로토타이핑용.
732
+
733
+ ```tsx
734
+ import { DefaultErrorComponent } from '@tanstack/react-router'
735
+
736
+ export const Route = createFileRoute('/dashboard')({
737
+ loader: async () => fetchDashboard(),
738
+ errorComponent: DefaultErrorComponent,
739
+ component: Dashboard,
740
+ })
741
+ ```
742
+
743
+ </default_error>
744
+
745
+ ---
746
+
747
+ <data_access_in_error>
748
+
749
+ ## 에러 상황에서 데이터 접근
750
+
751
+ 각 에러 컴포넌트에서 접근 가능한 데이터.
752
+
753
+ ### errorComponent
754
+
755
+ Loader 데이터 접근 불가 (에러 발생했으므로).
756
+
757
+ ```tsx
758
+ const ErrorHandler = ({ error, reset }: ErrorComponentProps) => {
759
+ // loader 데이터 없음
760
+ // const data = Route.useLoaderData() // 에러 발생
761
+
762
+ // params, search, context는 접근 가능
763
+ const params = Route.useParams()
764
+ const search = Route.useSearch()
765
+ const context = Route.useRouteContext()
766
+
767
+ return (
768
+ <div>
769
+ <p>Error on route: {params}</p>
770
+ <button onClick={reset}>Retry</button>
771
+ </div>
772
+ )
773
+ }
774
+ ```
775
+
776
+ ### notFoundComponent
777
+
778
+ Loader 데이터 없음. params/search/context만 접근. data 옵션으로 불완전한 데이터 전달 가능.
779
+
780
+ ```tsx
781
+ const NotFoundHandler = ({ data }: { data?: any }) => {
782
+ // Loader 데이터 없음
783
+ // const { item } = Route.useLoaderData()
784
+
785
+ // params, search, context는 접근 가능
786
+ const { itemId } = Route.useParams()
787
+ const search = Route.useSearch()
788
+ const context = Route.useRouteContext()
789
+
790
+ return (
791
+ <div>
792
+ <p>Item {itemId} not found</p>
793
+ <p>You searched for: {search}</p>
794
+ </div>
795
+ )
796
+ }
797
+ ```
798
+
799
+ | 컴포넌트 | useLoaderData | useParams | useSearch | useRouteContext | data 옵션 |
800
+ |----------|---------------|-----------|-----------|----------------|-----------|
801
+ | errorComponent | 불가 | 가능 | 가능 | 가능 | - |
802
+ | notFoundComponent | 불가 | 가능 | 가능 | 가능 | 가능 |
803
+ | pendingComponent | 불가 | 가능 | 가능 | 가능 | - |
804
+ | component | 가능 | 가능 | 가능 | 가능 | - |
805
+
806
+ </data_access_in_error>
807
+
808
+ ---
809
+
810
+ <dos_donts>
811
+
812
+ ## Do's & Don'ts
813
+
814
+ | Do | Don't |
815
+ |-------|---------|
816
+ | `throw new Error()` for server errors | Error throw 하지 말고 데이터 반환 |
817
+ | `throw notFound()` for missing resources | 404를 Error로 throw |
818
+ | `errorComponent`로 에러 처리 | try-catch in component |
819
+ | `pendingComponent`로 로딩 표시 | useQuery isLoading |
820
+ | `router.invalidate()`로 loader 에러 복구 | `reset()`만 호출 (loader 재실행 안 됨) |
821
+ | `ErrorComponent`로 폴백 처리 | 모든 에러를 커스텀 처리 |
822
+ | Catch-all `$` 라우트로 404 | 모든 라우트에 notFoundComponent |
823
+ | `notFoundMode: 'fuzzy'`로 레이아웃 보존 | root에서만 404 처리 |
824
+ | Root notFoundComponent 정의 | 기본 not found 그대로 사용 |
825
+ | `onError`로 에러 로깅 | 에러 무시 |
826
+ | `defaultNotFoundComponent` 라우터 설정 | 각 라우트별로 다른 404 UI |
827
+
828
+ </dos_donts>