@kood/claude-code 0.6.5 → 0.6.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +255 -149
- package/package.json +1 -1
- package/templates/.claude/agents/researcher.md +8 -1
- package/templates/.claude/instructions/sourcing/reliable-search.md +49 -2
- package/templates/.claude/scripts/deploy/build-run.sh +36 -0
- package/templates/.claude/scripts/deploy/deploy-check.sh +38 -0
- package/templates/.claude/scripts/git/git-all.sh +57 -0
- package/templates/.claude/scripts/git/git-clean-check.sh +31 -0
- package/templates/.claude/scripts/git/git-commit.sh +51 -0
- package/templates/.claude/scripts/git/git-info.sh +34 -0
- package/templates/.claude/scripts/git/git-push.sh +50 -0
- package/templates/.claude/scripts/lint/lint-check.sh +56 -0
- package/templates/.claude/scripts/lint/lint-file.sh +41 -0
- package/templates/.claude/scripts/pm/pm-detect.sh +25 -0
- package/templates/.claude/scripts/pm/pm-run.sh +41 -0
- package/templates/.claude/scripts/version/version-bump.sh +54 -0
- package/templates/.claude/scripts/version/version-find.sh +49 -0
- package/templates/.claude/skills/docs-fetch/SKILL.md +5 -4
- package/templates/.claude/skills/project-optimizer/AGENTS.md +275 -0
- package/templates/.claude/skills/project-optimizer/SKILL.md +374 -0
- package/templates/.claude/skills/project-optimizer/rules/arch-config-centralize.md +66 -0
- package/templates/.claude/skills/project-optimizer/rules/arch-hot-path.md +35 -0
- package/templates/.claude/skills/project-optimizer/rules/arch-interface-segregation.md +51 -0
- package/templates/.claude/skills/project-optimizer/rules/arch-module-boundary.md +42 -0
- package/templates/.claude/skills/project-optimizer/rules/build-cache.md +57 -0
- package/templates/.claude/skills/project-optimizer/rules/build-code-split.md +56 -0
- package/templates/.claude/skills/project-optimizer/rules/build-incremental.md +65 -0
- package/templates/.claude/skills/project-optimizer/rules/build-minify.md +61 -0
- package/templates/.claude/skills/project-optimizer/rules/build-tree-shake.md +60 -0
- package/templates/.claude/skills/project-optimizer/rules/code-complexity.md +65 -0
- package/templates/.claude/skills/project-optimizer/rules/code-dead-elimination.md +32 -0
- package/templates/.claude/skills/project-optimizer/rules/code-duplication.md +54 -0
- package/templates/.claude/skills/project-optimizer/rules/code-error-handling.md +75 -0
- package/templates/.claude/skills/project-optimizer/rules/code-naming.md +52 -0
- package/templates/.claude/skills/project-optimizer/rules/concurrency-defer-await.md +54 -0
- package/templates/.claude/skills/project-optimizer/rules/concurrency-parallel.md +90 -0
- package/templates/.claude/skills/project-optimizer/rules/concurrency-pipeline.md +68 -0
- package/templates/.claude/skills/project-optimizer/rules/concurrency-pool.md +68 -0
- package/templates/.claude/skills/project-optimizer/rules/deps-lightweight-alt.md +37 -0
- package/templates/.claude/skills/project-optimizer/rules/deps-peer-align.md +44 -0
- package/templates/.claude/skills/project-optimizer/rules/deps-security-audit.md +45 -0
- package/templates/.claude/skills/project-optimizer/rules/deps-unused-removal.md +25 -0
- package/templates/.claude/skills/project-optimizer/rules/deps-version-pin.md +40 -0
- package/templates/.claude/skills/project-optimizer/rules/dx-ci-speed.md +47 -0
- package/templates/.claude/skills/project-optimizer/rules/dx-dev-server.md +35 -0
- package/templates/.claude/skills/project-optimizer/rules/dx-lint-config.md +36 -0
- package/templates/.claude/skills/project-optimizer/rules/dx-test-coverage.md +34 -0
- package/templates/.claude/skills/project-optimizer/rules/dx-type-safety.md +49 -0
- package/templates/.claude/skills/project-optimizer/rules/io-batch-queries.md +67 -0
- package/templates/.claude/skills/project-optimizer/rules/io-cache-layer.md +67 -0
- package/templates/.claude/skills/project-optimizer/rules/io-connection-reuse.md +67 -0
- package/templates/.claude/skills/project-optimizer/rules/io-serialize-minimal.md +61 -0
- package/templates/.claude/skills/project-optimizer/rules/io-stream.md +75 -0
- package/templates/.claude/skills/project-optimizer/rules/memory-bounded-cache.md +65 -0
- package/templates/.claude/skills/project-optimizer/rules/memory-large-data.md +64 -0
- package/templates/.claude/skills/project-optimizer/rules/memory-lazy-init.md +78 -0
- package/templates/.claude/skills/project-optimizer/rules/memory-leak-prevention.md +79 -0
- package/templates/.claude/skills/project-optimizer/rules/memory-pool-reuse.md +70 -0
- package/templates/.claude/skills/sql-optimizer/SKILL.md +437 -0
- package/templates/.claude/skills/sql-optimizer/orm-patterns.md +218 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/AGENTS.md +53 -14
- package/templates/.claude/skills/tanstack-start-react-best-practices/SKILL.md +93 -27
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/bundle-defer-third-party.md +42 -19
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/client-optimistic-updates.md +109 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/client-suspense-query.md +74 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/client-use-hook.md +81 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/rerender-react-compiler.md +81 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-beforeload-auth.md +121 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-file-conventions.md +104 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-link-navigation.md +119 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-nested-layouts.md +155 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-path-params.md +89 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-pending-component.md +110 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-preload-strategy.md +91 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-router-context.md +120 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-search-params.md +114 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-deferred-data.md +1 -1
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-error-boundaries.md +79 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-middleware.md +85 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-serialization.md +56 -21
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-streaming.md +84 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-validator.md +71 -0
- package/templates/.claude/skills/tauri-react-best-practices/AGENTS.md +527 -0
- package/templates/.claude/skills/tauri-react-best-practices/SKILL.md +570 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/bundle-barrel-imports.md +140 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/bundle-cargo-profile.md +96 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/bundle-frontend-treeshake.md +242 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/bundle-lazy-components.md +255 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/bundle-remove-unused-commands.md +160 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/deploy-ci-pipeline.md +269 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/deploy-signing.md +207 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/deploy-updater.md +226 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-async-commands.md +172 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-batch-commands.md +133 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-binary-response.md +198 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-channel-streaming.md +186 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-error-handling.md +250 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-type-safe.md +227 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/perf-derived-state.md +231 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/perf-functional-setstate.md +191 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/perf-index-maps.md +276 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/perf-lazy-state-init.md +196 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/plugin-lifecycle.md +265 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/plugin-mobile-compat.md +199 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/plugin-permission-scope.md +193 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/react-error-boundary.md +239 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/react-event-listener.md +151 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/react-file-src.md +155 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/react-invoke-hook.md +139 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/react-optimistic-update.md +211 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/security-capability-split.md +205 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/security-csp.md +207 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/security-least-privilege.md +106 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/security-no-wildcard.md +253 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/security-scope-paths.md +160 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/state-async-mutex.md +270 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/state-mutex-pattern.md +265 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/state-react-sync.md +375 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/state-single-container.md +275 -0
- package/templates/tanstack-start/docs/architecture.md +238 -167
- package/templates/tanstack-start/docs/library/tanstack-router/error-handling.md +777 -38
- package/templates/tanstack-start/docs/library/tanstack-router/hooks.md +549 -37
- package/templates/tanstack-start/docs/library/tanstack-router/index.md +895 -111
- package/templates/tanstack-start/docs/library/tanstack-router/navigation.md +641 -43
- package/templates/tanstack-start/docs/library/tanstack-router/route-context.md +889 -38
- package/templates/tanstack-start/docs/library/tanstack-router/search-params.md +891 -29
- package/templates/tanstack-start/docs/library/tanstack-start/auth-patterns.md +972 -36
- package/templates/tanstack-start/docs/library/tanstack-start/index.md +1525 -881
- package/templates/tanstack-start/docs/library/tanstack-start/middleware.md +1099 -20
- package/templates/tanstack-start/docs/library/tanstack-start/routing.md +796 -30
- package/templates/tanstack-start/docs/library/tanstack-start/server-functions.md +953 -35
- package/templates/tanstack-start/docs/library/tanstack-start/setup.md +371 -15
- package/templates/tauri/CLAUDE.md +189 -0
- package/templates/tauri/docs/guides/distribution.md +261 -0
- package/templates/tauri/docs/guides/getting-started.md +302 -0
- package/templates/tauri/docs/guides/mobile.md +288 -0
- package/templates/tauri/docs/library/tauri/index.md +510 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use Route-Level Error Boundaries
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: graceful error recovery per route
|
|
5
|
+
tags: server, error-handling, error-boundary, tanstack-router
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 라우트 레벨 에러 바운더리
|
|
9
|
+
|
|
10
|
+
TanStack Router는 라우트별 `errorComponent`를 지원합니다. 전역 에러 페이지 대신 라우트 단위 에러 처리로 사용자 경험을 개선하세요.
|
|
11
|
+
|
|
12
|
+
**❌ 잘못된 예시 (에러 처리 없음):**
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
export const Route = createFileRoute('/posts/$postId')({
|
|
16
|
+
loader: async ({ params }) => {
|
|
17
|
+
const post = await getPost(params.postId)
|
|
18
|
+
return { post } // post가 null이면 런타임 에러
|
|
19
|
+
},
|
|
20
|
+
component: PostPage
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
function PostPage() {
|
|
24
|
+
const { post } = Route.useLoaderData()
|
|
25
|
+
return <div>{post.title}</div> // 💥 Cannot read property 'title' of null
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**✅ 올바른 예시 (라우트별 에러 처리):**
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
export const Route = createFileRoute('/posts/$postId')({
|
|
33
|
+
loader: async ({ params }) => {
|
|
34
|
+
const post = await getPost(params.postId)
|
|
35
|
+
if (!post) throw new Error('Post not found')
|
|
36
|
+
return { post }
|
|
37
|
+
},
|
|
38
|
+
errorComponent: ({ error, reset }) => (
|
|
39
|
+
<div>
|
|
40
|
+
<h2>오류가 발생했습니다</h2>
|
|
41
|
+
<p>{error.message}</p>
|
|
42
|
+
<button onClick={reset}>다시 시도</button>
|
|
43
|
+
</div>
|
|
44
|
+
),
|
|
45
|
+
component: PostPage
|
|
46
|
+
})
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**전역 기본 에러 컴포넌트 설정:**
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { createRouter } from '@tanstack/react-router'
|
|
53
|
+
|
|
54
|
+
const router = createRouter({
|
|
55
|
+
routeTree,
|
|
56
|
+
defaultErrorComponent: ({ error, reset }) => (
|
|
57
|
+
<div>
|
|
58
|
+
<h1>예기치 못한 오류</h1>
|
|
59
|
+
<p>{error.message}</p>
|
|
60
|
+
<button onClick={reset}>재시도</button>
|
|
61
|
+
</div>
|
|
62
|
+
)
|
|
63
|
+
})
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**notFoundComponent로 404 처리:**
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
export const Route = createFileRoute('/posts/$postId')({
|
|
70
|
+
loader: async ({ params }) => {
|
|
71
|
+
const post = await getPost(params.postId)
|
|
72
|
+
if (!post) throw notFound()
|
|
73
|
+
return { post }
|
|
74
|
+
},
|
|
75
|
+
notFoundComponent: () => <div>게시글을 찾을 수 없습니다</div>
|
|
76
|
+
})
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**원칙:** loader에서 예외를 명시적으로 throw하고, errorComponent/notFoundComponent로 처리합니다. TanStack Router가 자동으로 Suspense와 ErrorBoundary를 래핑합니다.
|
package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-middleware.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use Middleware for Cross-Cutting Concerns
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: centralizes auth, logging, validation
|
|
5
|
+
tags: server, middleware, createMiddleware, authentication, tanstack-start
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 크로스 커팅 관심사에 Middleware 사용
|
|
9
|
+
|
|
10
|
+
`createMiddleware()`로 인증, 로깅, 에러 처리 등 공통 로직을 중앙화합니다. 각 Server Function에 반복 코드를 작성하지 마세요.
|
|
11
|
+
|
|
12
|
+
**❌ 잘못된 예시 (모든 Server Function에 인증 로직 반복):**
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
16
|
+
|
|
17
|
+
const getTodos = createServerFn().handler(async () => {
|
|
18
|
+
const session = await getSession()
|
|
19
|
+
if (!session?.user) throw new Error('Unauthorized')
|
|
20
|
+
return await db.todo.findMany({ where: { userId: session.user.id } })
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const createTodo = createServerFn({ method: 'POST' })
|
|
24
|
+
.handler(async (data) => {
|
|
25
|
+
const session = await getSession()
|
|
26
|
+
if (!session?.user) throw new Error('Unauthorized')
|
|
27
|
+
return await db.todo.create({ data: { ...data, userId: session.user.id } })
|
|
28
|
+
})
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**✅ 올바른 예시 (Middleware로 인증 중앙화):**
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
35
|
+
import { createMiddleware } from '@tanstack/react-start'
|
|
36
|
+
|
|
37
|
+
// 인증 미들웨어 정의 (재사용 가능)
|
|
38
|
+
const authMiddleware = createMiddleware()
|
|
39
|
+
.server(async ({ next }) => {
|
|
40
|
+
const session = await getSession()
|
|
41
|
+
if (!session?.user) {
|
|
42
|
+
throw redirect({ to: '/login' })
|
|
43
|
+
}
|
|
44
|
+
return next({ context: { user: session.user } })
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
// Server Functions에 미들웨어 적용
|
|
48
|
+
const getTodos = createServerFn()
|
|
49
|
+
.middleware([authMiddleware])
|
|
50
|
+
.handler(async ({ context }) => {
|
|
51
|
+
return await db.todo.findMany({ where: { userId: context.user.id } })
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const createTodo = createServerFn({ method: 'POST' })
|
|
55
|
+
.middleware([authMiddleware])
|
|
56
|
+
.inputValidator((d: unknown) => TodoSchema.parse(d))
|
|
57
|
+
.handler(async ({ data, context }) => {
|
|
58
|
+
return await db.todo.create({ data: { ...data, userId: context.user.id } })
|
|
59
|
+
})
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**미들웨어 체이닝:**
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
const loggingMiddleware = createMiddleware()
|
|
66
|
+
.server(async ({ next }) => {
|
|
67
|
+
const start = Date.now()
|
|
68
|
+
const result = await next()
|
|
69
|
+
console.log(`Duration: ${Date.now() - start}ms`)
|
|
70
|
+
return result
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
// 여러 미들웨어 조합 (배열 순서대로 실행)
|
|
74
|
+
const protectedAction = createServerFn({ method: 'POST' })
|
|
75
|
+
.middleware([loggingMiddleware, authMiddleware])
|
|
76
|
+
.handler(async ({ context }) => { /* ... */ })
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**실행 순서:** inputValidator → middleware (배열 순서) → handler
|
|
80
|
+
|
|
81
|
+
**주의사항:**
|
|
82
|
+
- 미들웨어 코드가 클라이언트 번들에 포함될 수 있으므로, DB 연결/API 키 등 민감 정보는 handler 내에서만 사용
|
|
83
|
+
- `redirect()` 사용 시 직렬화 에러가 발생할 수 있으므로 `throw redirect()` 패턴 사용
|
|
84
|
+
|
|
85
|
+
참고: [Middleware Guide](https://tanstack.com/start/latest/docs/framework/react/guide/middleware)
|
package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-serialization.md
CHANGED
|
@@ -1,38 +1,73 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: Minimize Serialization
|
|
2
|
+
title: Minimize Serialization in Loader Data
|
|
3
3
|
impact: HIGH
|
|
4
4
|
impactDescription: reduces data transfer size
|
|
5
|
-
tags: server,
|
|
5
|
+
tags: server, loader, serialization, tanstack-start
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Loader 데이터 직렬화 최소화
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
TanStack Start의 loader에서 반환하는 데이터는 서버 → 클라이언트로 직렬화되어 전달됩니다. 이 직렬화된 데이터는 HTML 응답 크기와 하이드레이션 시간에 직접 영향을 미치므로 **클라이언트가 실제로 사용하는 필드만 반환하세요**.
|
|
11
11
|
|
|
12
12
|
**❌ 잘못된 예시 (50개 필드 모두 직렬화):**
|
|
13
13
|
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
```typescript
|
|
15
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
16
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
17
|
+
|
|
18
|
+
const getUser = createServerFn().handler(async () => {
|
|
19
|
+
return await db.user.findUnique({ where: { id: '1' } }) // 50개 필드
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
export const Route = createFileRoute('/profile')({
|
|
23
|
+
loader: async () => {
|
|
24
|
+
const user = await getUser()
|
|
25
|
+
return { user } // 50개 필드 모두 직렬화
|
|
26
|
+
}
|
|
27
|
+
})
|
|
19
28
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return <div>{user.name}</div>
|
|
29
|
+
function ProfilePage() {
|
|
30
|
+
const { user } = Route.useLoaderData()
|
|
31
|
+
return <div>{user.name}</div> // 1개 필드만 사용
|
|
23
32
|
}
|
|
24
33
|
```
|
|
25
34
|
|
|
26
|
-
**✅ 올바른 예시 (
|
|
35
|
+
**✅ 올바른 예시 (필요한 필드만 직렬화):**
|
|
27
36
|
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
const user = await
|
|
31
|
-
return
|
|
32
|
-
}
|
|
37
|
+
```typescript
|
|
38
|
+
const getUser = createServerFn().handler(async () => {
|
|
39
|
+
const user = await db.user.findUnique({ where: { id: '1' } })
|
|
40
|
+
return { name: user.name, email: user.email } // 필요한 필드만
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
export const Route = createFileRoute('/profile')({
|
|
44
|
+
loader: async () => {
|
|
45
|
+
const user = await getUser()
|
|
46
|
+
return { userName: user.name, userEmail: user.email }
|
|
47
|
+
}
|
|
48
|
+
})
|
|
33
49
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return
|
|
50
|
+
function ProfilePage() {
|
|
51
|
+
const { userName, userEmail } = Route.useLoaderData()
|
|
52
|
+
return (
|
|
53
|
+
<div>
|
|
54
|
+
<div>{userName}</div>
|
|
55
|
+
<div>{userEmail}</div>
|
|
56
|
+
</div>
|
|
57
|
+
)
|
|
37
58
|
}
|
|
38
59
|
```
|
|
60
|
+
|
|
61
|
+
**✅ Server Function에서 select로 필드 제한:**
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
const getUser = createServerFn().handler(async () => {
|
|
65
|
+
// DB 쿼리 자체에서 필드 제한 (가장 효율적)
|
|
66
|
+
return await db.user.findUnique({
|
|
67
|
+
where: { id: '1' },
|
|
68
|
+
select: { name: true, email: true, avatar: true }
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
loader 데이터가 클수록 SSR HTML 크기, 하이드레이션 시간, 클라이언트 네비게이션 데이터 전송량이 모두 증가합니다.
|
package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-streaming.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Stream Data from Server Functions
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: progressive data delivery
|
|
5
|
+
tags: server, streaming, async-generator, ReadableStream, tanstack-start
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Server Functions에서 데이터 스트리밍
|
|
9
|
+
|
|
10
|
+
대량 데이터나 실시간 업데이트가 필요한 경우, Server Function에서 스트리밍으로 데이터를 점진적 전달합니다.
|
|
11
|
+
|
|
12
|
+
**❌ 잘못된 예시 (전체 데이터 대기):**
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
const generateReport = createServerFn({ method: 'POST' })
|
|
16
|
+
.handler(async () => {
|
|
17
|
+
const items = []
|
|
18
|
+
for (const chunk of await processLargeDataset()) {
|
|
19
|
+
items.push(chunk)
|
|
20
|
+
}
|
|
21
|
+
return items // 전체 처리 완료까지 대기
|
|
22
|
+
})
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**✅ 올바른 예시 (async generator로 스트리밍):**
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
29
|
+
|
|
30
|
+
const generateReport = createServerFn({ method: 'POST' })
|
|
31
|
+
.handler(async function* () {
|
|
32
|
+
yield { status: 'processing', progress: 0 }
|
|
33
|
+
|
|
34
|
+
for await (const chunk of processLargeDataset()) {
|
|
35
|
+
yield { status: 'processing', data: chunk, progress: chunk.index }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
yield { status: 'complete', progress: 100 }
|
|
39
|
+
})
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**✅ ReadableStream 방식 (세밀한 제어):**
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
const streamResponse = createServerFn({ method: 'POST' })
|
|
46
|
+
.handler(async () => {
|
|
47
|
+
return new ReadableStream({
|
|
48
|
+
start(controller) {
|
|
49
|
+
const encoder = new TextEncoder()
|
|
50
|
+
controller.enqueue(encoder.encode(JSON.stringify({ msg: 'chunk 1' })))
|
|
51
|
+
controller.enqueue(encoder.encode(JSON.stringify({ msg: 'chunk 2' })))
|
|
52
|
+
controller.close()
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**클라이언트에서 스트림 소비:**
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
function ReportGenerator() {
|
|
62
|
+
const [chunks, setChunks] = useState<ReportChunk[]>([])
|
|
63
|
+
|
|
64
|
+
const handleGenerate = async () => {
|
|
65
|
+
const stream = await generateReport()
|
|
66
|
+
for await (const chunk of stream) {
|
|
67
|
+
setChunks(prev => [...prev, chunk])
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div>
|
|
73
|
+
<button onClick={handleGenerate}>Generate</button>
|
|
74
|
+
{chunks.map((chunk, i) => <ChunkDisplay key={i} chunk={chunk} />)}
|
|
75
|
+
</div>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**사용 시점:** AI 응답 스트리밍, 대용량 CSV/JSON 내보내기, 실시간 진행률 표시, 로그 스트리밍.
|
|
81
|
+
|
|
82
|
+
**주의:** ReadableStream 사용 시 UTF-8 인코딩을 위해 `TextEncoder.encode()` 필수. async generator가 더 간결하고 타입 안전합니다.
|
|
83
|
+
|
|
84
|
+
참고: [Streaming Data from Server Functions](https://tanstack.com/start/latest/docs/framework/react/guide/streaming-data-from-server-functions)
|
package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-validator.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use inputValidator for Type-Safe Server Functions
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: runtime type safety with Zod
|
|
5
|
+
tags: server, validation, inputValidator, zod, tanstack-start
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## inputValidator로 타입 안전한 Server Functions
|
|
9
|
+
|
|
10
|
+
`createServerFn`의 `.inputValidator()`로 런타임 입력 검증과 TypeScript 타입 추론을 동시에 확보합니다.
|
|
11
|
+
|
|
12
|
+
**❌ 잘못된 예시 (검증 없음, 타입 안전하지 않음):**
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
const createUser = createServerFn({ method: 'POST' })
|
|
16
|
+
.handler(async (data: any) => {
|
|
17
|
+
// data가 검증되지 않음 - 런타임 에러 위험
|
|
18
|
+
return await db.user.create({ data })
|
|
19
|
+
})
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**✅ 올바른 예시 (Zod로 검증 + 타입 추론):**
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { z } from 'zod'
|
|
26
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
27
|
+
|
|
28
|
+
const UserSchema = z.object({
|
|
29
|
+
name: z.string().min(1),
|
|
30
|
+
email: z.string().email()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const createUser = createServerFn({ method: 'POST' })
|
|
34
|
+
.inputValidator((d: unknown) => UserSchema.parse(d))
|
|
35
|
+
.handler(async ({ data }) => {
|
|
36
|
+
// data는 자동으로 { name: string; email: string } 타입
|
|
37
|
+
return await db.user.create({ data })
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
// 클라이언트에서 호출
|
|
41
|
+
await createUser({ data: { name: 'Alice', email: 'alice@example.com' } })
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**중요:** `.inputValidator()`는 반드시 **함수** 형태로 전달해야 합니다. Zod schema를 직접 전달하면 안 됩니다.
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
// ❌ 잘못됨: schema 직접 전달
|
|
48
|
+
.inputValidator(UserSchema)
|
|
49
|
+
|
|
50
|
+
// ✅ 올바름: 함수로 감싸서 전달
|
|
51
|
+
.inputValidator((d: unknown) => UserSchema.parse(d))
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**FormData 검증:**
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
const uploadFile = createServerFn({ method: 'POST' })
|
|
58
|
+
.inputValidator((d: unknown) => {
|
|
59
|
+
if (!(d instanceof FormData)) throw new Error('Expected FormData')
|
|
60
|
+
return {
|
|
61
|
+
name: d.get('name')?.toString() || '',
|
|
62
|
+
file: d.get('file') as File
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
.handler(async ({ data }) => {
|
|
66
|
+
// data.name, data.file 모두 타입 안전
|
|
67
|
+
return await saveFile(data.file)
|
|
68
|
+
})
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
참고: [Server Functions Guide](https://tanstack.com/start/latest/docs/framework/react/guide/server-functions)
|