@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,10 +1,138 @@
1
1
  # TanStack Start - Middleware
2
2
 
3
- Server Function 및 라우트에 공통 로직 적용.
3
+ > Server Functions 및 라우트에 공통 로직 적용 (v1.159.4)
4
4
 
5
- ## 기본 패턴
5
+ ---
6
+
7
+ <overview>
8
+
9
+ ## Middleware란?
10
+
11
+ Middleware는 Server Function 또는 라우트 핸들러 실행 전에 공통 로직을 처리하는 함수입니다.
12
+
13
+ | 용도 | 예시 |
14
+ |------|------|
15
+ | **인증** | 세션/토큰 검증 |
16
+ | **권한** | 역할 기반 접근 제어 |
17
+ | **검증** | 입력값 Zod 검증 |
18
+ | **로깅** | 요청/응답 로깅 |
19
+ | **CORS** | 교차 출처 요청 처리 |
20
+ | **CSP** | Content Security Policy 설정 |
21
+ | **Observability** | 메트릭, 트레이스, 로그 |
22
+ | **Context 제공** | 요청 객체에 데이터 첨부 |
23
+ | **에러 처리** | 일관된 에러 핸들링 |
24
+
25
+ </overview>
26
+
27
+ ---
28
+
29
+ <middleware_types>
30
+
31
+ ## 미들웨어 타입
32
+
33
+ TanStack Start는 두 가지 미들웨어 타입을 제공합니다:
34
+
35
+ | 타입 | 범위 | 메서드 | 입력 검증 | 클라이언트 로직 |
36
+ |------|------|--------|----------|----------------|
37
+ | **Request Middleware** | 모든 서버 요청 (SSR, Server Routes, Server Functions) | `.server()` | 없음 | 없음 |
38
+ | **Server Function Middleware** | Server Functions만 | `.client()`, `.server()` | `.inputValidator()` | 있음 |
39
+
40
+ > **참고:** Request Middleware는 Server Function Middleware에 의존할 수 없지만, Server Function Middleware는 Request Middleware에 의존할 수 있습니다.
41
+
42
+ ### Request Middleware
43
+
44
+ 모든 서버 요청 (SSR, Server Routes, Server Functions)에 적용됩니다.
45
+
46
+ ```typescript
47
+ import { createMiddleware } from '@tanstack/react-start'
48
+
49
+ // 기본 (type 생략 시 'request'가 기본값)
50
+ const loggingMiddleware = createMiddleware().server(({ next }) => {
51
+ console.log('Processing request')
52
+ return next()
53
+ })
54
+
55
+ // 명시적 type 지정
56
+ const explicitRequestMiddleware = createMiddleware({ type: 'request' }).server(
57
+ ({ next, context, request }) => {
58
+ return next()
59
+ },
60
+ )
61
+ ```
62
+
63
+ **사용 가능한 메서드:**
64
+ - `middleware`: 체인에 다른 미들웨어 추가
65
+ - `server`: 서버 사이드 로직 정의
66
+
67
+ ### Server Function Middleware
68
+
69
+ Server Functions 전용으로, 클라이언트/서버 양쪽에서 로직 실행이 가능합니다.
70
+
71
+ ```typescript
72
+ const functionMiddleware = createMiddleware({ type: 'function' })
73
+ .client(async ({ next, context }) => {
74
+ console.log('Client: before RPC')
75
+ const result = await next()
76
+ console.log('Client: after RPC')
77
+ return result
78
+ })
79
+ .server(async ({ next }) => {
80
+ console.log('Server: executing')
81
+ return next()
82
+ })
83
+ ```
84
+
85
+ **사용 가능한 메서드 (순서 중요 - TypeScript가 강제):**
86
+ 1. `middleware`: 체인에 다른 미들웨어 추가
87
+ 2. `inputValidator`: 데이터 검증
88
+ 3. `client`: 클라이언트 사이드 로직 (RPC 호출 전후)
89
+ 4. `server`: 서버 사이드 로직
90
+
91
+ </middleware_types>
92
+
93
+ ---
94
+
95
+ <execution_flow>
96
+
97
+ ## 실행 흐름
98
+
99
+ ### Request Middleware 흐름
100
+
101
+ ```
102
+ HTTP Request
103
+ -> Middleware.server (next() 호출)
104
+ -> ServerFn (payload 처리)
105
+ <- result 반환
106
+ <- return
107
+ HTTP Response
108
+ ```
109
+
110
+ ### Server Function Middleware 흐름
111
+
112
+ ```
113
+ ServerFn (client) -> Middleware.client (payload)
114
+ -> Middleware.client: next()
115
+ -> Middleware.server (Request)
116
+ -> Middleware.server: next()
117
+ -> ServerFn (payload 처리)
118
+ <- result 반환
119
+ <- return
120
+ <- Response
121
+ <- return
122
+ ServerFn (client) <- result
123
+ ```
124
+
125
+ </execution_flow>
126
+
127
+ ---
128
+
129
+ <basic_pattern>
130
+
131
+ ## 기본 미들웨어 패턴
6
132
 
7
133
  ```typescript
134
+ import { createMiddleware, createServerFn } from '@tanstack/react-start'
135
+
8
136
  // 미들웨어 정의
9
137
  const loggingMiddleware = createMiddleware({ type: 'function' })
10
138
  .server(({ next }) => {
@@ -12,53 +140,1004 @@ const loggingMiddleware = createMiddleware({ type: 'function' })
12
140
  return next()
13
141
  })
14
142
 
15
- // 적용
16
- const fn = createServerFn()
143
+ // Server Function에 적용
144
+ export const sayHello = createServerFn({ method: 'GET' })
17
145
  .middleware([loggingMiddleware])
18
- .handler(async () => ({ message: 'Hello' }))
146
+ .handler(async (): Promise<{ message: string }> => {
147
+ return { message: 'Hello' }
148
+ })
149
+ ```
150
+
151
+ ### 미들웨어 조합 (Composition)
152
+
153
+ 미들웨어는 다른 미들웨어에 의존할 수 있습니다.
154
+
155
+ ```typescript
156
+ const loggingMiddleware = createMiddleware().server(() => {
157
+ //...
158
+ })
159
+
160
+ const authMiddleware = createMiddleware()
161
+ .middleware([loggingMiddleware]) // loggingMiddleware에 의존
162
+ .server(() => {
163
+ //...
164
+ })
165
+ ```
166
+
167
+ ### next()로 체인 진행
168
+
169
+ 모든 미들웨어는 `next()` 함수를 호출해야 다음 미들웨어가 실행됩니다:
170
+
171
+ ```typescript
172
+ const loggingMiddleware = createMiddleware().server(async ({ next }) => {
173
+ const result = await next() // 다음 미들웨어 실행
174
+ return result
175
+ })
176
+ ```
177
+
178
+ </basic_pattern>
179
+
180
+ ---
181
+
182
+ <context_management>
183
+
184
+ ## Context 관리
185
+
186
+ ### Context 전달 (next를 통해)
187
+
188
+ `next()` 호출 시 `context` 객체를 전달하면 하위 미들웨어에 데이터를 제공합니다.
189
+
190
+ ```typescript
191
+ const awesomeMiddleware = createMiddleware({ type: 'function' }).server(
192
+ ({ next }) => {
193
+ return next({
194
+ context: {
195
+ isAwesome: Math.random() > 0.5,
196
+ },
197
+ })
198
+ },
199
+ )
200
+
201
+ const loggingMiddleware = createMiddleware({ type: 'function' })
202
+ .middleware([awesomeMiddleware])
203
+ .server(async ({ next, context }) => {
204
+ console.log('Is awesome?', context.isAwesome) // 타입 안전
205
+ return next()
206
+ })
207
+ ```
208
+
209
+ ### Context 누적
210
+
211
+ 미들웨어가 순차적으로 context를 누적합니다.
212
+
213
+ ```typescript
214
+ const middleware1 = createMiddleware({ type: 'function' })
215
+ .server(({ next }) => next({ context: { data1: 'value1' } }))
216
+
217
+ const middleware2 = createMiddleware({ type: 'function' })
218
+ .server(({ next, context }) => next({
219
+ context: { ...context, data2: 'value2' }
220
+ }))
221
+
222
+ export const fn = createServerFn({ method: 'GET' })
223
+ .middleware([middleware1, middleware2])
224
+ .handler(async ({ context }): Promise<unknown> => {
225
+ // context = { data1: 'value1', data2: 'value2' }
226
+ return context
227
+ })
19
228
  ```
20
229
 
230
+ </context_management>
231
+
232
+ ---
233
+
234
+ <send_context>
235
+
236
+ ## sendContext: 클라이언트-서버 간 컨텍스트 전송
237
+
238
+ ### 클라이언트에서 서버로 (Client -> Server)
239
+
240
+ **클라이언트 컨텍스트는 기본적으로 서버로 전송되지 않습니다.** 대규모 페이로드가 의도치 않게 서버로 전송되는 것을 방지하기 위해서입니다. `sendContext`를 사용하면 명시적으로 데이터를 서버에 전송할 수 있습니다.
241
+
242
+ ```typescript
243
+ import { createMiddleware } from '@tanstack/react-start'
244
+
245
+ const requestLogger = createMiddleware({ type: 'function' })
246
+ .client(async ({ next, context }) => {
247
+ return next({
248
+ sendContext: {
249
+ // 클라이언트의 workspaceId를 서버로 전송
250
+ workspaceId: context.workspaceId,
251
+ },
252
+ })
253
+ })
254
+ .server(async ({ next, data, context }) => {
255
+ // 서버에서 클라이언트가 보낸 workspaceId 접근 가능
256
+ console.log('Workspace ID:', context.workspaceId)
257
+ return next()
258
+ })
259
+ ```
260
+
261
+ #### sendContext 보안 주의사항
262
+
263
+ `sendContext`로 전달된 데이터는 타입 안전하지만, 런타임 검증이 자동으로 이루어지지 않습니다. **동적 사용자 데이터를 전송할 때는 반드시 서버에서 검증해야 합니다.**
264
+
265
+ ```typescript
266
+ import { createMiddleware } from '@tanstack/react-start'
267
+ import { zodValidator } from '@tanstack/zod-adapter'
268
+ import { z } from 'zod'
269
+
270
+ const requestLogger = createMiddleware({ type: 'function' })
271
+ .client(async ({ next, context }) => {
272
+ return next({
273
+ sendContext: {
274
+ workspaceId: context.workspaceId,
275
+ },
276
+ })
277
+ })
278
+ .server(async ({ next, data, context }) => {
279
+ // 사용 전 반드시 검증
280
+ const workspaceId = zodValidator(z.number()).parse(context.workspaceId)
281
+ console.log('Workspace ID:', workspaceId)
282
+ return next()
283
+ })
284
+ ```
285
+
286
+ ### 서버에서 클라이언트로 (Server -> Client)
287
+
288
+ 서버에서도 `sendContext`를 통해 클라이언트로 데이터를 전송할 수 있습니다. 전송된 데이터는 클라이언트 미들웨어의 `next()` 반환값의 `context`에서 접근 가능합니다.
289
+
290
+ > **주의:** `next()` 반환값의 타입은 현재 미들웨어 체인에서 알려진 미들웨어에서만 추론됩니다. 따라서 체인 마지막 미들웨어에서 가장 정확한 타입을 얻을 수 있습니다.
291
+
292
+ ```typescript
293
+ import { createMiddleware } from '@tanstack/react-start'
294
+
295
+ // 서버에서 시간 정보를 클라이언트로 전송
296
+ const serverTimer = createMiddleware({ type: 'function' }).server(
297
+ async ({ next }) => {
298
+ return next({
299
+ sendContext: {
300
+ timeFromServer: new Date(),
301
+ },
302
+ })
303
+ },
304
+ )
305
+
306
+ const requestLogger = createMiddleware({ type: 'function' })
307
+ .middleware([serverTimer])
308
+ .client(async ({ next }) => {
309
+ const result = await next()
310
+ // 서버에서 보낸 시간 정보에 접근 가능
311
+ console.log('Time from the server:', result.context.timeFromServer)
312
+ return result
313
+ })
314
+ ```
315
+
316
+ </send_context>
317
+
318
+ ---
319
+
320
+ <custom_headers>
321
+
322
+ ## 커스텀 헤더 설정
323
+
324
+ ### 클라이언트 미들웨어에서 헤더 추가
325
+
326
+ `.client()` 메서드에서 `next()`에 `headers` 객체를 전달하여 요청 헤더를 설정할 수 있습니다.
327
+
328
+ ```typescript
329
+ import { createMiddleware } from '@tanstack/react-start'
330
+ import { getToken } from 'my-auth-library'
331
+
332
+ const authMiddleware = createMiddleware({ type: 'function' }).client(
333
+ async ({ next }) => {
334
+ return next({
335
+ headers: {
336
+ Authorization: `Bearer ${getToken()}`,
337
+ },
338
+ })
339
+ },
340
+ )
341
+ ```
342
+
343
+ ### 헤더 머징 (다중 미들웨어)
344
+
345
+ 여러 미들웨어가 헤더를 설정하면, 모두 **병합**됩니다. 후속 미들웨어가 동일 키의 헤더를 덮어씁니다.
346
+
347
+ ```typescript
348
+ const firstMiddleware = createMiddleware({ type: 'function' }).client(
349
+ async ({ next }) => {
350
+ return next({
351
+ headers: {
352
+ 'X-Request-ID': '12345',
353
+ 'X-Source': 'first-middleware',
354
+ },
355
+ })
356
+ },
357
+ )
358
+
359
+ const secondMiddleware = createMiddleware({ type: 'function' }).client(
360
+ async ({ next }) => {
361
+ return next({
362
+ headers: {
363
+ 'X-Timestamp': Date.now().toString(),
364
+ 'X-Source': 'second-middleware', // first를 덮어씀
365
+ },
366
+ })
367
+ },
368
+ )
369
+
370
+ // 최종 헤더:
371
+ // - X-Request-ID: '12345' (first에서)
372
+ // - X-Timestamp: '<timestamp>' (second에서)
373
+ // - X-Source: 'second-middleware' (second가 first를 덮어씀)
374
+ ```
375
+
376
+ ### 호출 시점에서 헤더 설정
377
+
378
+ ```typescript
379
+ await myServerFn({
380
+ data: { name: 'John' },
381
+ headers: {
382
+ 'X-Custom-Header': 'call-site-value',
383
+ },
384
+ })
385
+ ```
386
+
387
+ **헤더 우선순위 (모두 병합, 후순위가 덮어씀):**
388
+
389
+ | 우선순위 | 출처 | 설명 |
390
+ |---------|------|------|
391
+ | 1 (낮음) | 앞쪽 미들웨어 | 먼저 설정된 헤더 |
392
+ | 2 | 뒤쪽 미들웨어 | 앞쪽 미들웨어를 덮어씀 |
393
+ | 3 (높음) | 호출 시점 | 모든 미들웨어 헤더를 덮어씀 |
394
+
395
+ </custom_headers>
396
+
397
+ ---
398
+
399
+ <custom_fetch>
400
+
401
+ ## 커스텀 fetch 구현
402
+
403
+ 고급 사용 사례에서 커스텀 `fetch` 구현을 제공하여 서버 함수 요청 방식을 제어할 수 있습니다:
404
+
405
+ - 요청 인터셉터 또는 재시도 로직
406
+ - 커스텀 HTTP 클라이언트 사용
407
+ - 테스트 및 모킹
408
+ - 텔레메트리/모니터링 추가
409
+
410
+ ### 클라이언트 미들웨어를 통한 커스텀 fetch
411
+
412
+ ```typescript
413
+ import { createMiddleware } from '@tanstack/react-start'
414
+ import type { CustomFetch } from '@tanstack/react-start'
415
+
416
+ const customFetchMiddleware = createMiddleware({ type: 'function' }).client(
417
+ async ({ next }) => {
418
+ const customFetch: CustomFetch = async (url, init) => {
419
+ console.log('Request starting:', url)
420
+ const start = Date.now()
421
+
422
+ const response = await fetch(url, init)
423
+
424
+ console.log('Request completed in', Date.now() - start, 'ms')
425
+ return response
426
+ }
427
+
428
+ return next({ fetch: customFetch })
429
+ },
430
+ )
431
+ ```
432
+
433
+ ### 호출 시점에서 커스텀 fetch
434
+
435
+ ```typescript
436
+ import type { CustomFetch } from '@tanstack/react-start'
437
+
438
+ const myFetch: CustomFetch = async (url, init) => {
439
+ // 커스텀 로직 추가
440
+ return fetch(url, init)
441
+ }
442
+
443
+ await myServerFn({
444
+ data: { name: 'John' },
445
+ fetch: myFetch,
446
+ })
447
+ ```
448
+
449
+ ### fetch 우선순위
450
+
451
+ | 우선순위 | 출처 | 설명 |
452
+ |---------|------|------|
453
+ | 1 (최고) | 호출 시점 | `serverFn({ fetch: customFetch })` |
454
+ | 2 | 뒤쪽 미들웨어 | 체인 마지막 미들웨어의 fetch |
455
+ | 3 | 앞쪽 미들웨어 | 체인 첫 미들웨어의 fetch |
456
+ | 4 | createStart | `createStart({ serverFns: { fetch: customFetch } })` |
457
+ | 5 (최저) | 기본값 | 전역 `fetch` 함수 |
458
+
459
+ **핵심 원칙:** 호출 시점이 항상 우선입니다. 특정 호출에서 미들웨어 동작을 재정의할 수 있습니다.
460
+
461
+ ### 전역 fetch (createStart)
462
+
463
+ 모든 서버 함수에 대한 기본 커스텀 fetch 설정:
464
+
465
+ ```typescript
466
+ // src/start.ts
467
+ import { createStart } from '@tanstack/react-start'
468
+ import type { CustomFetch } from '@tanstack/react-start'
469
+
470
+ const globalFetch: CustomFetch = async (url, init) => {
471
+ console.log('Global fetch:', url)
472
+ return fetch(url, init)
473
+ }
474
+
475
+ export const startInstance = createStart(() => {
476
+ return {
477
+ serverFns: {
478
+ fetch: globalFetch,
479
+ },
480
+ }
481
+ })
482
+ ```
483
+
484
+ > **참고:** 커스텀 fetch는 클라이언트 사이드에서만 적용됩니다. SSR 중에는 서버 함수가 fetch 없이 직접 호출됩니다.
485
+
486
+ </custom_fetch>
487
+
488
+ ---
489
+
490
+ <auth_middleware>
491
+
21
492
  ## 인증 미들웨어
22
493
 
494
+ ### 세션 기반 인증
495
+
23
496
  ```typescript
497
+ import { createMiddleware } from '@tanstack/react-start'
498
+ import { redirect } from '@tanstack/react-router'
499
+
24
500
  const authMiddleware = createMiddleware({ type: 'function' })
25
- .server(async ({ next }) => {
26
- const session = await getSession()
27
- if (!session) throw redirect({ to: '/login' })
501
+ .server(async ({ next, request }) => {
502
+ const session = await getSession(request)
503
+
504
+ if (!session?.user) {
505
+ throw redirect({ to: '/login' })
506
+ }
507
+
28
508
  return next({ context: { user: session.user } })
29
509
  })
30
510
 
31
511
  // 사용
32
- export const protectedFn = createServerFn({ method: 'GET' })
512
+ export const getMyProfile = createServerFn({ method: 'GET' })
33
513
  .middleware([authMiddleware])
34
- .handler(async ({ context }) => ({ user: context.user }))
514
+ .handler(async ({ context }): Promise<User> => {
515
+ return prisma.user.findUnique({
516
+ where: { id: context.user.id },
517
+ })
518
+ })
519
+ ```
520
+
521
+ ### 선택적 인증
522
+
523
+ ```typescript
524
+ const optionalAuthMiddleware = createMiddleware({ type: 'function' })
525
+ .server(async ({ next, request }) => {
526
+ const session = await getSession(request)
527
+ return next({ context: { user: session?.user ?? null } })
528
+ })
529
+
530
+ export const getPublicPosts = createServerFn({ method: 'GET' })
531
+ .middleware([optionalAuthMiddleware])
532
+ .handler(async ({ context }): Promise<Post[]> => {
533
+ return prisma.post.findMany({
534
+ where: {
535
+ published: true,
536
+ ...(context.user && { authorId: context.user.id }),
537
+ },
538
+ })
539
+ })
540
+ ```
541
+
542
+ </auth_middleware>
543
+
544
+ ---
545
+
546
+ <role_middleware>
547
+
548
+ ## 역할 기반 미들웨어
549
+
550
+ ### 고정 역할 (Admin)
551
+
552
+ ```typescript
553
+ const adminMiddleware = createMiddleware({ type: 'function' })
554
+ .server(async ({ next, request }) => {
555
+ const session = await getSession(request)
556
+
557
+ if (!session?.user || session.user.role !== 'ADMIN') {
558
+ throw new Error('Forbidden: Admin access only')
559
+ }
560
+
561
+ return next({ context: { user: session.user } })
562
+ })
563
+
564
+ export const deleteAnyUser = createServerFn({ method: 'DELETE' })
565
+ .middleware([adminMiddleware])
566
+ .inputValidator(z.object({ id: z.string() }))
567
+ .handler(async ({ data }): Promise<{ success: true }> => {
568
+ await prisma.user.delete({ where: { id: data.id } })
569
+ return { success: true }
570
+ })
35
571
  ```
36
572
 
37
- ## Zod Validation Middleware
573
+ ### 동적 역할
38
574
 
39
575
  ```typescript
576
+ const roleMiddleware = (allowedRoles: string[]) =>
577
+ createMiddleware({ type: 'function' })
578
+ .server(async ({ next, request }) => {
579
+ const session = await getSession(request)
580
+
581
+ if (!session?.user) {
582
+ throw redirect({ to: '/login' })
583
+ }
584
+
585
+ if (!allowedRoles.includes(session.user.role)) {
586
+ throw new Error(`Forbidden: Required roles: ${allowedRoles.join(', ')}`)
587
+ }
588
+
589
+ return next({ context: { user: session.user } })
590
+ })
591
+
592
+ export const approvePost = createServerFn({ method: 'POST' })
593
+ .middleware([roleMiddleware(['ADMIN', 'MODERATOR'])])
594
+ .inputValidator(z.object({ id: z.string() }))
595
+ .handler(async ({ data }): Promise<Post> => {
596
+ return prisma.post.update({
597
+ where: { id: data.id },
598
+ data: { published: true },
599
+ })
600
+ })
601
+ ```
602
+
603
+ </role_middleware>
604
+
605
+ ---
606
+
607
+ <validation_middleware>
608
+
609
+ ## 검증 미들웨어
610
+
611
+ ### Zod 검증 미들웨어
612
+
613
+ ```typescript
614
+ import { zodValidator } from '@tanstack/zod-adapter'
615
+ import { z } from 'zod'
616
+
617
+ const mySchema = z.object({
618
+ workspaceId: z.string(),
619
+ })
620
+
40
621
  const workspaceMiddleware = createMiddleware({ type: 'function' })
41
- .inputValidator(zodValidator(z.object({ workspaceId: z.string() })))
42
- .server(({ next, data }) => next())
622
+ .inputValidator(zodValidator(mySchema))
623
+ .server(async ({ next, data }) => {
624
+ const workspace = await prisma.workspace.findUnique({
625
+ where: { id: data.workspaceId },
626
+ })
627
+
628
+ if (!workspace) {
629
+ throw new Error('Workspace not found')
630
+ }
631
+
632
+ return next({ context: { workspace } })
633
+ })
634
+
635
+ export const getWorkspaceData = createServerFn({ method: 'GET' })
636
+ .middleware([authMiddleware, workspaceMiddleware])
637
+ .handler(async ({ context }): Promise<WorkspaceData> => {
638
+ return {
639
+ workspace: context.workspace,
640
+ user: context.user,
641
+ projects: await prisma.project.findMany({
642
+ where: { workspaceId: context.workspace.id },
643
+ }),
644
+ }
645
+ })
646
+ ```
647
+
648
+ ### 비즈니스 로직 검증
649
+
650
+ ```typescript
651
+ const projectAccessMiddleware = createMiddleware({ type: 'function' })
652
+ .inputValidator(z.object({ projectId: z.string() }))
653
+ .server(async ({ next, data, request }) => {
654
+ const session = await getSession(request)
655
+ if (!session?.user) throw redirect({ to: '/login' })
656
+
657
+ const project = await prisma.project.findUnique({
658
+ where: { id: data.projectId },
659
+ include: { members: true },
660
+ })
661
+
662
+ if (!project) {
663
+ throw new Error('Project not found')
664
+ }
665
+
666
+ const isMember = project.members.some((m) => m.userId === session.user.id)
667
+ const isOwner = project.ownerId === session.user.id
668
+
669
+ if (!isMember && !isOwner) {
670
+ throw new Error('Access denied')
671
+ }
672
+
673
+ return next({
674
+ context: {
675
+ user: session.user,
676
+ project,
677
+ isOwner,
678
+ },
679
+ })
680
+ })
681
+ ```
682
+
683
+ </validation_middleware>
684
+
685
+ ---
686
+
687
+ <middleware_chaining>
688
+
689
+ ## 미들웨어 체이닝
690
+
691
+ ### 순서가 중요
692
+
693
+ ```typescript
694
+ export const protectedWorkspaceFn = createServerFn({ method: 'POST' })
695
+ .middleware([
696
+ loggingMiddleware, // 1. 로깅 (모든 요청)
697
+ authMiddleware, // 2. 인증 (세션 검증)
698
+ roleMiddleware(['ADMIN']), // 3. 권한 (역할 확인)
699
+ workspaceMiddleware, // 4. 비즈니스 로직 (workspace 검증)
700
+ ])
701
+ .inputValidator(taskSchema)
702
+ .handler(async ({ data, context }): Promise<Task> => {
703
+ return prisma.task.create({
704
+ data: {
705
+ ...data,
706
+ workspaceId: context.workspace.id,
707
+ createdById: context.user.id,
708
+ },
709
+ })
710
+ })
43
711
  ```
44
712
 
45
- ## Global Middleware
713
+ ### 미들웨어 실행 순서
714
+
715
+ 미들웨어는 의존성 우선으로 실행됩니다. 전역 미들웨어가 먼저, 그 다음 서버 함수 미들웨어가 실행됩니다.
716
+
717
+ ```typescript
718
+ // 실행 순서: globalMiddleware1 -> globalMiddleware2 -> a -> b -> c -> d -> fn
719
+ const a = createMiddleware({ type: 'function' }).server(async ({ next }) => {
720
+ console.log('a')
721
+ return next()
722
+ })
723
+
724
+ const b = createMiddleware({ type: 'function' })
725
+ .middleware([a])
726
+ .server(async ({ next }) => {
727
+ console.log('b')
728
+ return next()
729
+ })
730
+
731
+ const c = createMiddleware({ type: 'function' }).server(async ({ next }) => {
732
+ console.log('c')
733
+ return next()
734
+ })
735
+
736
+ const d = createMiddleware({ type: 'function' })
737
+ .middleware([b, c])
738
+ .server(async ({ next }) => {
739
+ console.log('d')
740
+ return next()
741
+ })
742
+
743
+ const fn = createServerFn()
744
+ .middleware([d])
745
+ .handler(async () => {
746
+ console.log('fn')
747
+ })
748
+ ```
749
+
750
+ </middleware_chaining>
751
+
752
+ ---
753
+
754
+ <global_middleware>
755
+
756
+ ## 전역 미들웨어
757
+
758
+ > **참고:** `src/start.ts` 파일은 기본 TanStack Start 템플릿에 포함되어 있지 않습니다. 전역 미들웨어를 구성하려면 이 파일을 직접 생성해야 합니다.
759
+
760
+ ### 전역 Request Middleware
761
+
762
+ 모든 요청 (Server Routes, SSR, Server Functions)에 적용:
46
763
 
47
764
  ```typescript
48
765
  // src/start.ts
766
+ import { createStart, createMiddleware } from '@tanstack/react-start'
767
+
768
+ const myGlobalMiddleware = createMiddleware().server(({ next }) => {
769
+ console.log('모든 요청에 실행')
770
+ return next()
771
+ })
772
+
49
773
  export const startInstance = createStart(() => ({
50
- requestMiddleware: [globalMiddleware], // 모든 요청
51
- functionMiddleware: [loggingMiddleware], // 모든 Server Function
774
+ requestMiddleware: [myGlobalMiddleware],
52
775
  }))
53
776
  ```
54
777
 
55
- ## Route-Level
778
+ ### 전역 Server Function Middleware
779
+
780
+ 모든 Server Function에 적용:
781
+
782
+ ```typescript
783
+ // src/start.ts
784
+ import { createStart, createMiddleware } from '@tanstack/react-start'
785
+
786
+ const loggingMiddleware = createMiddleware({ type: 'function' })
787
+ .server(({ next, request }) => {
788
+ console.log(`[${new Date().toISOString()}] ${request.method}`)
789
+ return next()
790
+ })
791
+
792
+ export const startInstance = createStart(() => ({
793
+ functionMiddleware: [loggingMiddleware],
794
+ }))
795
+ ```
796
+
797
+ ### 전역 에러 처리
798
+
799
+ ```typescript
800
+ const errorHandlingMiddleware = createMiddleware({ type: 'function' })
801
+ .server(async ({ next }) => {
802
+ try {
803
+ return await next()
804
+ } catch (error) {
805
+ if (error instanceof Error) {
806
+ console.error('Error:', error.message)
807
+ }
808
+ throw error
809
+ }
810
+ })
811
+
812
+ export const startInstance = createStart(() => ({
813
+ functionMiddleware: [errorHandlingMiddleware],
814
+ }))
815
+ ```
816
+
817
+ </global_middleware>
818
+
819
+ ---
820
+
821
+ <route_middleware>
822
+
823
+ ## 라우트 레벨 미들웨어
824
+
825
+ ### 모든 Server Route 메서드에 적용
826
+
827
+ ```typescript
828
+ export const Route = createFileRoute('/api/protected')({
829
+ server: {
830
+ middleware: [authMiddleware, adminMiddleware],
831
+ handlers: {
832
+ GET: async ({ request }): Promise<Response> => {
833
+ return new Response('Admin data')
834
+ },
835
+ POST: async ({ request }): Promise<Response> => {
836
+ const body = await request.json()
837
+ return Response.json({ created: true })
838
+ },
839
+ },
840
+ },
841
+ })
842
+ ```
843
+
844
+ ### 특정 메서드에만 미들웨어 적용
845
+
846
+ `createHandlers` 유틸리티를 사용하여 개별 메서드에 미들웨어를 적용합니다.
847
+
848
+ ```typescript
849
+ export const Route = createFileRoute('/foo')({
850
+ server: {
851
+ handlers: ({ createHandlers }) =>
852
+ createHandlers({
853
+ GET: {
854
+ middleware: [loggingMiddleware],
855
+ handler: () => {
856
+ //...
857
+ },
858
+ },
859
+ }),
860
+ },
861
+ })
862
+ ```
863
+
864
+ ### beforeLoad와 미들웨어
56
865
 
57
866
  ```typescript
58
- export const Route = createFileRoute('/hello')({
867
+ export const Route = createFileRoute('/dashboard')({
868
+ // 라우트 진입 전 검증
869
+ beforeLoad: async () => {
870
+ const user = await getCurrentUser()
871
+ if (!user) {
872
+ throw redirect({ to: '/login' })
873
+ }
874
+ return { user }
875
+ },
876
+
59
877
  server: {
60
- middleware: [authMiddleware], // 모든 핸들러
61
- handlers: { GET: async ({ request }) => new Response('Hello') },
878
+ middleware: [loggingMiddleware],
879
+ handlers: {
880
+ GET: async (): Promise<Response> => {
881
+ return new Response('Dashboard data')
882
+ },
883
+ },
62
884
  },
885
+
886
+ component: DashboardPage,
63
887
  })
64
888
  ```
889
+
890
+ </route_middleware>
891
+
892
+ ---
893
+
894
+ <request_response>
895
+
896
+ ## 요청/응답 수정
897
+
898
+ ### 서버 사이드: 요청/응답 유틸리티
899
+
900
+ `.server()` 메서드 내에서 Server Function Context 유틸리티를 사용할 수 있습니다:
901
+
902
+ ```typescript
903
+ import {
904
+ getRequest,
905
+ getRequestHeader,
906
+ setResponseHeaders,
907
+ setResponseStatus,
908
+ } from '@tanstack/react-start/server'
909
+
910
+ const cachingMiddleware = createMiddleware({ type: 'function' })
911
+ .server(async ({ next }) => {
912
+ const request = getRequest()
913
+ const authHeader = getRequestHeader('Authorization')
914
+
915
+ setResponseHeaders(
916
+ new Headers({
917
+ 'Cache-Control': 'public, max-age=300',
918
+ }),
919
+ )
920
+
921
+ setResponseStatus(200)
922
+
923
+ return next()
924
+ })
925
+ ```
926
+
927
+ **사용 가능한 유틸리티:**
928
+ - `getRequest()` - 전체 Request 객체 접근
929
+ - `getRequestHeader(name)` - 특정 요청 헤더 읽기
930
+ - `setResponseHeader(name, value)` - 단일 응답 헤더 설정
931
+ - `setResponseHeaders(headers)` - Headers 객체로 다중 응답 헤더 설정
932
+ - `setResponseStatus(code)` - HTTP 상태 코드 설정
933
+
934
+ </request_response>
935
+
936
+ ---
937
+
938
+ <error_handling>
939
+
940
+ ## 미들웨어 에러 처리
941
+
942
+ ### 리다이렉트
943
+
944
+ ```typescript
945
+ const authMiddleware = createMiddleware({ type: 'function' })
946
+ .server(async ({ next, request }) => {
947
+ const session = await getSession(request)
948
+
949
+ if (!session?.user) {
950
+ throw redirect({
951
+ to: '/login',
952
+ search: { returnUrl: '/dashboard' },
953
+ })
954
+ }
955
+
956
+ return next({ context: { user: session.user } })
957
+ })
958
+ ```
959
+
960
+ ### 커스텀 에러
961
+
962
+ ```typescript
963
+ class ForbiddenError extends Error {
964
+ constructor(message: string) {
965
+ super(message)
966
+ this.name = 'ForbiddenError'
967
+ }
968
+ }
969
+
970
+ const permissionMiddleware = createMiddleware({ type: 'function' })
971
+ .server(async ({ next, request }) => {
972
+ const session = await getSession(request)
973
+
974
+ if (!session?.user) {
975
+ throw new ForbiddenError('Unauthorized')
976
+ }
977
+
978
+ return next({ context: { user: session.user } })
979
+ })
980
+ ```
981
+
982
+ ### 에러 변환
983
+
984
+ ```typescript
985
+ const errorTransformMiddleware = createMiddleware({ type: 'function' })
986
+ .server(async ({ next }) => {
987
+ try {
988
+ return await next()
989
+ } catch (error) {
990
+ if (error instanceof ForbiddenError) {
991
+ throw new Error(`Access Denied: ${error.message}`)
992
+ }
993
+ throw error
994
+ }
995
+ })
996
+ ```
997
+
998
+ </error_handling>
999
+
1000
+ ---
1001
+
1002
+ <context_typing>
1003
+
1004
+ ## Context 타입 정의
1005
+
1006
+ ```typescript
1007
+ // lib/middleware-context.ts
1008
+
1009
+ export interface MiddlewareContext {
1010
+ user?: {
1011
+ id: string
1012
+ email: string
1013
+ role: 'USER' | 'ADMIN' | 'MODERATOR'
1014
+ }
1015
+ workspace?: {
1016
+ id: string
1017
+ name: string
1018
+ }
1019
+ permissions?: string[]
1020
+ }
1021
+
1022
+ // middleware/auth.ts
1023
+ const authMiddleware = createMiddleware({ type: 'function' })
1024
+ .server(async ({ next, request }) => {
1025
+ const session = await getSession(request)
1026
+
1027
+ return next({
1028
+ context: {
1029
+ user: session?.user ? {
1030
+ id: session.user.id,
1031
+ email: session.user.email,
1032
+ role: session.user.role as 'USER' | 'ADMIN',
1033
+ } : undefined,
1034
+ },
1035
+ })
1036
+ })
1037
+
1038
+ // Server Function
1039
+ export const fn = createServerFn({ method: 'GET' })
1040
+ .middleware([authMiddleware])
1041
+ .handler(async ({ context }): Promise<unknown> => {
1042
+ // context.user는 타입 안전
1043
+ return context.user?.id
1044
+ })
1045
+ ```
1046
+
1047
+ </context_typing>
1048
+
1049
+ ---
1050
+
1051
+ <environment_tree_shaking>
1052
+
1053
+ ## 환경별 트리 셰이킹
1054
+
1055
+ 미들웨어 기능은 각 번들의 환경에 따라 트리 셰이킹됩니다:
1056
+
1057
+ | 환경 | 동작 |
1058
+ |------|------|
1059
+ | **서버** | 모든 코드 포함 (트리 셰이킹 없음) |
1060
+ | **클라이언트** | `.server()` 코드 제거, `data` 검증 코드도 제거 |
1061
+
1062
+ </environment_tree_shaking>
1063
+
1064
+ ---
1065
+
1066
+ <best_practices>
1067
+
1068
+ ## 모범 사례
1069
+
1070
+ | 원칙 | 설명 |
1071
+ |------|------|
1072
+ | **단일 책임** | 각 미들웨어는 하나의 기능만 담당 |
1073
+ | **재사용성** | 공통 미들웨어는 `@/middleware/`에 |
1074
+ | **순서 명시** | 미들웨어 순서를 주석으로 표기 |
1075
+ | **에러 처리** | 미들웨어에서 throw로 명확하게 에러 처리 |
1076
+ | **Context 명확성** | context 구조를 타입으로 정의 |
1077
+ | **이름** | 미들웨어는 `*Middleware` 이름으로 통일 |
1078
+ | **sendContext 검증** | 클라이언트에서 보낸 동적 데이터는 서버에서 반드시 검증 |
1079
+ | **메서드 순서** | TypeScript 강제 순서 준수: middleware -> inputValidator -> client -> server |
1080
+
1081
+ ### 나쁜 예
1082
+
1083
+ ```typescript
1084
+ // 여러 책임을 가진 미들웨어
1085
+ const megaMiddleware = createMiddleware({ type: 'function' })
1086
+ .server(async ({ next }) => {
1087
+ console.log('Request') // 로깅
1088
+ const session = await getSession() // 인증
1089
+ if (session?.user.role !== 'ADMIN') throw new Error('Forbidden') // 권한
1090
+ return next()
1091
+ })
1092
+ ```
1093
+
1094
+ ### 좋은 예
1095
+
1096
+ ```typescript
1097
+ // 책임 분리
1098
+ const loggingMiddleware = createMiddleware({ type: 'function' })
1099
+ .server(({ next }) => {
1100
+ console.log('Request')
1101
+ return next()
1102
+ })
1103
+
1104
+ const authMiddleware = createMiddleware({ type: 'function' })
1105
+ .server(async ({ next }) => {
1106
+ const session = await getSession()
1107
+ if (!session) throw redirect({ to: '/login' })
1108
+ return next({ context: { user: session.user } })
1109
+ })
1110
+
1111
+ const adminMiddleware = createMiddleware({ type: 'function' })
1112
+ .server(async ({ next, context }) => {
1113
+ if (context.user.role !== 'ADMIN') {
1114
+ throw new Error('Forbidden')
1115
+ }
1116
+ return next()
1117
+ })
1118
+
1119
+ // 조합 사용
1120
+ export const adminFn = createServerFn({ method: 'POST' })
1121
+ .middleware([loggingMiddleware, authMiddleware, adminMiddleware])
1122
+ .handler(async () => ({ success: true }))
1123
+ ```
1124
+
1125
+ </best_practices>
1126
+
1127
+ ---
1128
+
1129
+ <version_info>
1130
+
1131
+ **Version:** TanStack Start/Router v1.159.4
1132
+
1133
+ **Key Features:**
1134
+ - Type-safe context passing (sendContext 포함)
1135
+ - Client-Server 양방향 컨텍스트 전송
1136
+ - 커스텀 헤더 설정 및 머징
1137
+ - 커스텀 fetch 구현 (미들웨어/호출 시점/전역)
1138
+ - Request/Server Function 두 가지 미들웨어 타입
1139
+ - 환경별 트리 셰이킹
1140
+ - 전역 및 라우트 레벨 미들웨어 지원
1141
+ - 특정 Server Route 메서드별 미들웨어 적용
1142
+
1143
+ </version_info>