@kood/claude-code 0.1.2 → 0.1.3

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 (61) hide show
  1. package/dist/index.js +12 -3
  2. package/package.json +2 -2
  3. package/templates/hono/CLAUDE.md +20 -2
  4. package/templates/hono/docs/architecture/architecture.md +909 -0
  5. package/templates/hono/docs/deployment/cloudflare.md +537 -190
  6. package/templates/hono/docs/deployment/docker.md +517 -0
  7. package/templates/hono/docs/deployment/index.md +181 -213
  8. package/templates/hono/docs/deployment/railway.md +416 -0
  9. package/templates/hono/docs/deployment/vercel.md +572 -0
  10. package/templates/hono/docs/git/git.md +285 -0
  11. package/templates/hono/docs/library/ai-sdk/index.md +427 -0
  12. package/templates/hono/docs/library/ai-sdk/openrouter.md +479 -0
  13. package/templates/hono/docs/library/ai-sdk/providers.md +468 -0
  14. package/templates/hono/docs/library/ai-sdk/streaming.md +447 -0
  15. package/templates/hono/docs/library/ai-sdk/structured-output.md +493 -0
  16. package/templates/hono/docs/library/ai-sdk/tools.md +513 -0
  17. package/templates/hono/docs/library/hono/env-setup.md +458 -0
  18. package/templates/hono/docs/library/hono/index.md +1 -0
  19. package/templates/hono/docs/library/pino/index.md +437 -0
  20. package/templates/hono/docs/library/prisma/cloudflare-d1.md +503 -0
  21. package/templates/hono/docs/library/prisma/config.md +362 -0
  22. package/templates/hono/docs/library/prisma/index.md +86 -13
  23. package/templates/hono/docs/skills/gemini-review/SKILL.md +116 -116
  24. package/templates/hono/docs/skills/gemini-review/references/checklists.md +125 -125
  25. package/templates/hono/docs/skills/gemini-review/references/prompt-templates.md +191 -191
  26. package/templates/npx/CLAUDE.md +309 -0
  27. package/templates/npx/docs/git/git.md +307 -0
  28. package/templates/npx/docs/library/commander/index.md +164 -0
  29. package/templates/npx/docs/library/fs-extra/index.md +171 -0
  30. package/templates/npx/docs/library/prompts/index.md +253 -0
  31. package/templates/npx/docs/mcp/index.md +60 -0
  32. package/templates/npx/docs/skills/gemini-review/SKILL.md +220 -0
  33. package/templates/npx/docs/skills/gemini-review/references/checklists.md +134 -0
  34. package/templates/npx/docs/skills/gemini-review/references/prompt-templates.md +301 -0
  35. package/templates/tanstack-start/CLAUDE.md +43 -5
  36. package/templates/tanstack-start/docs/architecture/architecture.md +134 -4
  37. package/templates/tanstack-start/docs/deployment/cloudflare.md +234 -51
  38. package/templates/tanstack-start/docs/deployment/index.md +322 -32
  39. package/templates/tanstack-start/docs/deployment/nitro.md +201 -20
  40. package/templates/tanstack-start/docs/deployment/railway.md +305 -153
  41. package/templates/tanstack-start/docs/deployment/vercel.md +353 -78
  42. package/templates/tanstack-start/docs/git/{index.md → git.md} +81 -7
  43. package/templates/tanstack-start/docs/guides/best-practices.md +203 -1
  44. package/templates/tanstack-start/docs/guides/env-setup.md +450 -0
  45. package/templates/tanstack-start/docs/library/ai-sdk/hooks.md +472 -0
  46. package/templates/tanstack-start/docs/library/ai-sdk/index.md +264 -0
  47. package/templates/tanstack-start/docs/library/ai-sdk/openrouter.md +371 -0
  48. package/templates/tanstack-start/docs/library/ai-sdk/providers.md +403 -0
  49. package/templates/tanstack-start/docs/library/ai-sdk/streaming.md +320 -0
  50. package/templates/tanstack-start/docs/library/ai-sdk/structured-output.md +454 -0
  51. package/templates/tanstack-start/docs/library/ai-sdk/tools.md +473 -0
  52. package/templates/tanstack-start/docs/library/pino/index.md +320 -0
  53. package/templates/tanstack-start/docs/library/prisma/cloudflare-d1.md +404 -0
  54. package/templates/tanstack-start/docs/library/prisma/config.md +377 -0
  55. package/templates/tanstack-start/docs/library/prisma/index.md +3 -1
  56. package/templates/tanstack-start/docs/library/prisma/schema.md +123 -25
  57. package/templates/tanstack-start/docs/library/tanstack-start/server-functions.md +80 -2
  58. package/templates/tanstack-start/docs/skills/gemini-review/SKILL.md +116 -116
  59. package/templates/tanstack-start/docs/skills/gemini-review/references/checklists.md +138 -144
  60. package/templates/tanstack-start/docs/skills/gemini-review/references/prompt-templates.md +186 -187
  61. package/templates/hono/docs/git/index.md +0 -180
@@ -146,6 +146,49 @@ services/
146
146
  └── mutations.ts
147
147
  ```
148
148
 
149
+ ## 코드 작성 규칙
150
+
151
+ ### UTF-8 인코딩 유지
152
+
153
+ 모든 한글 텍스트는 UTF-8 인코딩이 깨지지 않도록 작성합니다.
154
+
155
+ ### 한글 주석 작성 규칙
156
+
157
+ **묶음 단위로 한글 주석을 작성합니다** (너무 세세하게 X)
158
+
159
+ ```typescript
160
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
161
+ // 사용자 관련 상태
162
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
163
+ const [user, setUser] = useState<User | null>(null)
164
+ const [isLoading, setIsLoading] = useState(false)
165
+
166
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
167
+ // 데이터 조회
168
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
169
+ const { data: users } = useQuery({
170
+ queryKey: ['users'],
171
+ queryFn: () => getUsers(),
172
+ })
173
+
174
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
175
+ // 이벤트 핸들러
176
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
177
+ const handleSubmit = () => { /* ... */ }
178
+ const handleDelete = () => { /* ... */ }
179
+ ```
180
+
181
+ ### ❌ 너무 세세한 주석 (금지)
182
+
183
+ ```typescript
184
+ // ❌ 이렇게 하지 마세요
185
+ const [user, setUser] = useState(null) // 사용자 상태
186
+ const [isLoading, setIsLoading] = useState(false) // 로딩 상태
187
+ const [error, setError] = useState(null) // 에러 상태
188
+ ```
189
+
190
+ ---
191
+
149
192
  ## TypeScript Standards
150
193
 
151
194
  ### Use `const` for Functions
@@ -234,7 +277,164 @@ const UsersPage = (): JSX.Element => {
234
277
  }
235
278
  ```
236
279
 
237
- ### Page Hook
280
+ ### Custom Hook 작성 규칙
281
+
282
+ **Purpose**: 페이지 또는 섹션의 **모든 로직, 상태, 라이프사이클**을 중앙화합니다.
283
+
284
+ - 페이지 훅: 페이지 전체 로직 담당
285
+ - 섹션 훅: 해당 섹션의 로직만 담당 (섹션으로 분리한 경우)
286
+
287
+ ### ⚠️ 필수: Custom Hook 내부 순서
288
+
289
+ 훅 내부 코드는 **반드시 아래 순서**를 따릅니다:
290
+
291
+ ```
292
+ 1. State (useState, zustand store)
293
+ 2. Global Hooks (useParams, useNavigate, useQueryClient 등)
294
+ 3. React Query (useQuery → useMutation 순서)
295
+ 4. Event Handlers & Functions
296
+ 5. useMemo
297
+ 6. useEffect
298
+ ```
299
+
300
+ ### ✅ 올바른 Custom Hook 예시
301
+
302
+ ```typescript
303
+ // routes/users/-hooks/use-users.ts
304
+ import { useState, useMemo, useEffect, useCallback } from 'react'
305
+ import { useParams, useNavigate } from '@tanstack/react-router'
306
+ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
307
+ import { useServerFn } from '@tanstack/react-start'
308
+ import { useAuthStore } from '@/stores/auth'
309
+ import { getUsers, createUser, deleteUser } from '@/services/user'
310
+ import type { User } from '@/types'
311
+
312
+ interface UseUsersReturn {
313
+ users: User[] | undefined
314
+ filteredUsers: User[]
315
+ isLoading: boolean
316
+ error: Error | null
317
+ search: string
318
+ setSearch: (value: string) => void
319
+ handleCreate: (data: { email: string; name: string }) => void
320
+ handleDelete: (id: string) => void
321
+ isCreating: boolean
322
+ isDeleting: boolean
323
+ }
324
+
325
+ export const useUsers = (): UseUsersReturn => {
326
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
327
+ // 1. State (useState, zustand store)
328
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
329
+ const [search, setSearch] = useState('')
330
+ const { user: currentUser } = useAuthStore()
331
+
332
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
333
+ // 2. Global Hooks
334
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
335
+ const params = useParams({ from: '/users/$id' })
336
+ const navigate = useNavigate()
337
+ const queryClient = useQueryClient()
338
+
339
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
340
+ // 3. React Query (useQuery → useMutation)
341
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
342
+ const getUsersFn = useServerFn(getUsers)
343
+ const createUserFn = useServerFn(createUser)
344
+ const deleteUserFn = useServerFn(deleteUser)
345
+
346
+ const { data: users, isLoading, error } = useQuery({
347
+ queryKey: ['users'],
348
+ queryFn: () => getUsersFn(),
349
+ })
350
+
351
+ const createMutation = useMutation({
352
+ mutationFn: createUserFn,
353
+ onSuccess: () => {
354
+ queryClient.invalidateQueries({ queryKey: ['users'] })
355
+ },
356
+ })
357
+
358
+ const deleteMutation = useMutation({
359
+ mutationFn: deleteUserFn,
360
+ onSuccess: () => {
361
+ queryClient.invalidateQueries({ queryKey: ['users'] })
362
+ },
363
+ })
364
+
365
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
366
+ // 4. Event Handlers & Functions
367
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
368
+ const handleCreate = useCallback(
369
+ (data: { email: string; name: string }) => {
370
+ createMutation.mutate({ data })
371
+ },
372
+ [createMutation]
373
+ )
374
+
375
+ const handleDelete = useCallback(
376
+ (id: string) => {
377
+ deleteMutation.mutate({ data: id })
378
+ },
379
+ [deleteMutation]
380
+ )
381
+
382
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
383
+ // 5. useMemo
384
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
385
+ const filteredUsers = useMemo(() => {
386
+ if (!users) return []
387
+ if (!search) return users
388
+ return users.filter((user) =>
389
+ user.name.toLowerCase().includes(search.toLowerCase())
390
+ )
391
+ }, [users, search])
392
+
393
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
394
+ // 6. useEffect
395
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
396
+ useEffect(() => {
397
+ if (!currentUser) {
398
+ navigate({ to: '/login' })
399
+ }
400
+ }, [currentUser, navigate])
401
+
402
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
403
+ // Return
404
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
405
+ return {
406
+ users,
407
+ filteredUsers,
408
+ isLoading,
409
+ error,
410
+ search,
411
+ setSearch,
412
+ handleCreate,
413
+ handleDelete,
414
+ isCreating: createMutation.isPending,
415
+ isDeleting: deleteMutation.isPending,
416
+ }
417
+ }
418
+ ```
419
+
420
+ ### ❌ 잘못된 순서 (금지)
421
+
422
+ ```typescript
423
+ // ❌ 순서가 뒤섞인 잘못된 예시
424
+ export const useBadHook = () => {
425
+ const queryClient = useQueryClient() // ❌ Global Hook이 먼저
426
+
427
+ useEffect(() => { /* ... */ }, []) // ❌ useEffect가 중간에
428
+
429
+ const [state, setState] = useState() // ❌ State가 나중에
430
+
431
+ const { data } = useQuery({ /* ... */ }) // ❌ Query가 Effect 다음에
432
+
433
+ const computed = useMemo(() => {}, []) // ❌ useMemo 위치 잘못됨
434
+ }
435
+ ```
436
+
437
+ ### Page Hook (간단한 예시)
238
438
 
239
439
  ```typescript
240
440
  // routes/users/-hooks/use-users.ts
@@ -253,8 +453,10 @@ interface UseUsersReturn {
253
453
  }
254
454
 
255
455
  export const useUsers = (): UseUsersReturn => {
456
+ // 2. Global Hooks
256
457
  const queryClient = useQueryClient()
257
458
 
459
+ // 3. React Query (useQuery → useMutation)
258
460
  const { data: users, isLoading, error } = useQuery({
259
461
  queryKey: ['users'],
260
462
  queryFn: () => getUsers(),
@@ -0,0 +1,450 @@
1
+ # TanStack Start - 환경 변수 설정
2
+
3
+ > **상위 문서**: [Best Practices](./best-practices.md)
4
+
5
+ TanStack Start는 Vite 기반으로 동작하며, 환경별 `.env` 파일을 통해 환경 변수를 관리합니다.
6
+
7
+ ---
8
+
9
+ ## 핵심 개념
10
+
11
+ ### 서버 vs 클라이언트 환경 변수
12
+
13
+ ```
14
+ 서버 전용 → process.env.DATABASE_URL (노출 X)
15
+ 클라이언트용 → import.meta.env.VITE_* (노출 O)
16
+ ```
17
+
18
+ | 접근 방식 | 접근 가능 위치 | 용도 |
19
+ |-----------|---------------|------|
20
+ | `process.env.*` | Server Function, 서버 코드 | DB 연결, API 키, 시크릿 |
21
+ | `import.meta.env.VITE_*` | 클라이언트 + 서버 | 공개 설정, API URL |
22
+
23
+ ⚠️ **중요**: `VITE_` 접두사가 없는 변수는 클라이언트에 노출되지 않습니다.
24
+
25
+ ---
26
+
27
+ ## 환경 파일 구조
28
+
29
+ ```
30
+ 프로젝트/
31
+ ├── .env # 기본 환경 변수 (공통, 커밋 O)
32
+ ├── .env.development # 개발 환경 (커밋 O)
33
+ ├── .env.production # 프로덕션 환경 (커밋 O)
34
+ ├── .env.local # 로컬 오버라이드 (커밋 X, gitignore)
35
+ ├── .env.development.local # 개발 로컬 오버라이드 (커밋 X)
36
+ ├── .env.production.local # 프로덕션 로컬 오버라이드 (커밋 X)
37
+ └── app/
38
+ └── config/
39
+ └── env.ts # 환경 변수 검증 및 타입
40
+ ```
41
+
42
+ ### 로드 우선순위 (높은 순)
43
+
44
+ ```
45
+ 1. .env.{mode}.local # 최우선 (gitignore)
46
+ 2. .env.local # 로컬 오버라이드 (gitignore)
47
+ 3. .env.{mode} # 환경별 설정
48
+ 4. .env # 기본 설정
49
+ ```
50
+
51
+ ---
52
+
53
+ ## 환경 변수 파일 예시
54
+
55
+ ### .env (기본 설정, 커밋용)
56
+
57
+ ```env
58
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
59
+ # 기본 환경 변수 (모든 환경에서 공통)
60
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
61
+
62
+ # 클라이언트 공개 설정 (VITE_ 접두사 필수)
63
+ VITE_APP_NAME=My TanStack App
64
+ VITE_API_URL=https://api.example.com
65
+
66
+ # 서버 설정 템플릿 (실제 값은 .env.local에서 오버라이드)
67
+ DATABASE_URL=postgresql://localhost:5432/myapp_dev
68
+ REDIS_URL=redis://localhost:6379
69
+ ```
70
+
71
+ ### .env.development
72
+
73
+ ```env
74
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
75
+ # 개발 환경 설정
76
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
77
+
78
+ NODE_ENV=development
79
+
80
+ # 클라이언트 공개 설정
81
+ VITE_APP_NAME=My App (Dev)
82
+ VITE_API_URL=http://localhost:3001/api
83
+ VITE_DEBUG=true
84
+
85
+ # 서버 설정
86
+ DATABASE_URL=postgresql://postgres:postgres@localhost:5432/myapp_dev
87
+ DATABASE_POOL_SIZE=5
88
+
89
+ # 개발용 설정
90
+ LOG_LEVEL=debug
91
+ CORS_ORIGIN=http://localhost:3000
92
+ ```
93
+
94
+ ### .env.production
95
+
96
+ ```env
97
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
98
+ # 프로덕션 환경 설정
99
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
100
+
101
+ NODE_ENV=production
102
+
103
+ # 클라이언트 공개 설정
104
+ VITE_APP_NAME=My App
105
+ VITE_API_URL=https://api.myapp.com
106
+ VITE_DEBUG=false
107
+
108
+ # 서버 설정 (실제 값은 CI/CD 또는 호스팅 환경에서 주입)
109
+ DATABASE_POOL_SIZE=20
110
+
111
+ # 프로덕션 설정
112
+ LOG_LEVEL=info
113
+ CORS_ORIGIN=https://myapp.com
114
+ ```
115
+
116
+ ### .env.local (gitignore, 실제 시크릿)
117
+
118
+ ```env
119
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
120
+ # 로컬 개발용 시크릿 (절대 커밋 금지!)
121
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
122
+
123
+ # 데이터베이스
124
+ DATABASE_URL=postgresql://user:realpassword@localhost:5432/myapp_local
125
+
126
+ # 인증
127
+ JWT_SECRET=your-super-secret-jwt-key-at-least-32-chars
128
+ AUTH_SECRET=your-auth-secret-key
129
+
130
+ # 외부 서비스 API 키
131
+ OPENAI_API_KEY=sk-xxx
132
+ STRIPE_SECRET_KEY=sk_test_xxx
133
+ ```
134
+
135
+ ---
136
+
137
+ ## 타입 안전한 환경 변수 (Zod)
138
+
139
+ ### app/config/env.ts
140
+
141
+ ```typescript
142
+ // app/config/env.ts
143
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
144
+ // 환경 변수 검증 및 타입 정의
145
+ // 서버/클라이언트 환경 변수를 분리하여 관리합니다
146
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
147
+ import { z } from 'zod'
148
+
149
+ // 서버 환경 변수 스키마
150
+ const serverEnvSchema = z.object({
151
+ NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
152
+
153
+ // 데이터베이스
154
+ DATABASE_URL: z.string().url(),
155
+ DATABASE_POOL_SIZE: z.coerce.number().default(10),
156
+
157
+ // 인증
158
+ JWT_SECRET: z.string().min(32),
159
+ AUTH_SECRET: z.string().min(16).optional(),
160
+
161
+ // 외부 서비스 (선택적)
162
+ OPENAI_API_KEY: z.string().optional(),
163
+ STRIPE_SECRET_KEY: z.string().optional(),
164
+
165
+ // 설정
166
+ LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
167
+ CORS_ORIGIN: z.string().default('*'),
168
+ })
169
+
170
+ // 클라이언트 환경 변수 스키마 (VITE_ 접두사)
171
+ const clientEnvSchema = z.object({
172
+ VITE_APP_NAME: z.string(),
173
+ VITE_API_URL: z.string().url(),
174
+ VITE_DEBUG: z.coerce.boolean().default(false),
175
+ })
176
+
177
+ // 타입 추출
178
+ export type ServerEnv = z.infer<typeof serverEnvSchema>
179
+ export type ClientEnv = z.infer<typeof clientEnvSchema>
180
+
181
+ // 서버 환경 변수 파싱 (Server Function에서만 호출)
182
+ const parseServerEnv = (): ServerEnv => {
183
+ const result = serverEnvSchema.safeParse(process.env)
184
+
185
+ if (!result.success) {
186
+ console.error('❌ 서버 환경 변수 검증 실패:')
187
+ console.error(result.error.format())
188
+ throw new Error('Server environment validation failed')
189
+ }
190
+
191
+ return result.data
192
+ }
193
+
194
+ // 클라이언트 환경 변수 파싱
195
+ const parseClientEnv = (): ClientEnv => {
196
+ const result = clientEnvSchema.safeParse(import.meta.env)
197
+
198
+ if (!result.success) {
199
+ console.error('❌ 클라이언트 환경 변수 검증 실패:')
200
+ console.error(result.error.format())
201
+ throw new Error('Client environment validation failed')
202
+ }
203
+
204
+ return result.data
205
+ }
206
+
207
+ // Export (서버 환경 변수는 lazy evaluation)
208
+ let _serverEnv: ServerEnv | null = null
209
+
210
+ export const getServerEnv = (): ServerEnv => {
211
+ if (!_serverEnv) {
212
+ _serverEnv = parseServerEnv()
213
+ }
214
+ return _serverEnv
215
+ }
216
+
217
+ export const clientEnv = parseClientEnv()
218
+ ```
219
+
220
+ ---
221
+
222
+ ## 사용 예시
223
+
224
+ ### Server Function에서 사용
225
+
226
+ ```typescript
227
+ // app/server-functions/users.ts
228
+ import { createServerFn } from '@tanstack/react-start'
229
+ import { getServerEnv } from '~/config/env'
230
+
231
+ export const getUsers = createServerFn({ method: 'GET' })
232
+ .handler(async () => {
233
+ const env = getServerEnv()
234
+
235
+ // ✅ 서버 전용 환경 변수 사용
236
+ const config = {
237
+ url: env.DATABASE_URL,
238
+ maxConnections: env.DATABASE_POOL_SIZE,
239
+ ssl: env.NODE_ENV === 'production',
240
+ }
241
+
242
+ const db = await createConnection(config)
243
+ return db.user.findMany()
244
+ })
245
+ ```
246
+
247
+ ### 클라이언트 컴포넌트에서 사용
248
+
249
+ ```tsx
250
+ // app/components/AppHeader.tsx
251
+ import { clientEnv } from '~/config/env'
252
+
253
+ export const AppHeader = () => {
254
+ return (
255
+ <header>
256
+ {/* ✅ VITE_ 접두사 변수만 접근 가능 */}
257
+ <h1>{clientEnv.VITE_APP_NAME}</h1>
258
+
259
+ {clientEnv.VITE_DEBUG && (
260
+ <span className="text-red-500">Debug Mode</span>
261
+ )}
262
+ </header>
263
+ )
264
+ }
265
+ ```
266
+
267
+ ### 직접 접근 (검증 없이)
268
+
269
+ ```tsx
270
+ // Server Function 내부
271
+ const dbUrl = process.env.DATABASE_URL // ✅ 서버에서만
272
+
273
+ // 클라이언트 컴포넌트
274
+ const appName = import.meta.env.VITE_APP_NAME // ✅ VITE_ 접두사만
275
+ ```
276
+
277
+ ---
278
+
279
+ ## 인증 설정 예시
280
+
281
+ ### 서버 측 인증 설정
282
+
283
+ ```typescript
284
+ // app/lib/auth.ts
285
+ import { getServerEnv } from '~/config/env'
286
+
287
+ export const getAuthConfig = () => {
288
+ const env = getServerEnv()
289
+
290
+ return {
291
+ secret: env.AUTH_SECRET,
292
+ providers: {
293
+ auth0: {
294
+ domain: process.env.AUTH0_DOMAIN,
295
+ clientId: process.env.AUTH0_CLIENT_ID,
296
+ clientSecret: process.env.AUTH0_CLIENT_SECRET, // ✅ 서버 전용
297
+ },
298
+ },
299
+ }
300
+ }
301
+ ```
302
+
303
+ ### 클라이언트 측 인증 Provider
304
+
305
+ ```tsx
306
+ // app/components/AuthProvider.tsx
307
+ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
308
+ return (
309
+ <Auth0Provider
310
+ domain={import.meta.env.VITE_AUTH0_DOMAIN}
311
+ clientId={import.meta.env.VITE_AUTH0_CLIENT_ID}
312
+ // ❌ clientSecret은 여기에 없음 - 서버에만 존재
313
+ >
314
+ {children}
315
+ </Auth0Provider>
316
+ )
317
+ }
318
+ ```
319
+
320
+ ---
321
+
322
+ ## 필수 환경 변수 검증
323
+
324
+ ### 앱 시작 시 검증
325
+
326
+ ```typescript
327
+ // app/config/validation.ts
328
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
329
+ // 필수 환경 변수 검증
330
+ // 앱 시작 시 누락된 환경 변수를 조기에 발견합니다
331
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
332
+
333
+ const requiredServerEnv = [
334
+ 'DATABASE_URL',
335
+ 'JWT_SECRET',
336
+ ] as const
337
+
338
+ const requiredClientEnv = [
339
+ 'VITE_APP_NAME',
340
+ 'VITE_API_URL',
341
+ ] as const
342
+
343
+ export const validateEnv = () => {
344
+ // 서버 환경 변수 검증
345
+ for (const key of requiredServerEnv) {
346
+ if (!process.env[key]) {
347
+ throw new Error(`❌ Missing required server env: ${key}`)
348
+ }
349
+ }
350
+
351
+ // 클라이언트 환경 변수 검증
352
+ for (const key of requiredClientEnv) {
353
+ if (!import.meta.env[key]) {
354
+ throw new Error(`❌ Missing required client env: ${key}`)
355
+ }
356
+ }
357
+
358
+ console.log('✅ Environment variables validated')
359
+ }
360
+ ```
361
+
362
+ ---
363
+
364
+ ## .gitignore 설정
365
+
366
+ ```gitignore
367
+ # 환경 변수 파일
368
+ .env.local
369
+ .env.*.local
370
+ .env.development.local
371
+ .env.production.local
372
+
373
+ # 커밋해도 되는 파일 (시크릿 없음)
374
+ !.env
375
+ !.env.development
376
+ !.env.production
377
+ ```
378
+
379
+ ⚠️ **중요**: `.env.development`와 `.env.production`에는 **실제 시크릿을 절대 넣지 마세요!**
380
+ - 실제 시크릿은 `.env.local` 또는 CI/CD 환경 변수로 관리
381
+ - `.env.*` 파일에는 플레이스홀더나 기본값만 포함
382
+
383
+ ---
384
+
385
+ ## 빌드 모드
386
+
387
+ ### 개발 서버
388
+
389
+ ```bash
390
+ # 기본 development 모드
391
+ npm run dev
392
+
393
+ # .env.development + .env 로드
394
+ ```
395
+
396
+ ### 프로덕션 빌드
397
+
398
+ ```bash
399
+ # 기본 production 모드
400
+ npm run build
401
+
402
+ # .env.production + .env 로드
403
+ ```
404
+
405
+ ### 커스텀 모드
406
+
407
+ ```bash
408
+ # staging 모드로 빌드
409
+ npm run build -- --mode staging
410
+
411
+ # .env.staging + .env 로드
412
+ ```
413
+
414
+ ```env
415
+ # .env.staging
416
+ NODE_ENV=production
417
+ VITE_APP_NAME=My App (Staging)
418
+ VITE_API_URL=https://staging-api.myapp.com
419
+ ```
420
+
421
+ ---
422
+
423
+ ## TypeScript 타입 정의
424
+
425
+ ### vite-env.d.ts
426
+
427
+ ```typescript
428
+ // app/vite-env.d.ts
429
+ /// <reference types="vite/client" />
430
+
431
+ interface ImportMetaEnv {
432
+ readonly VITE_APP_NAME: string
433
+ readonly VITE_API_URL: string
434
+ readonly VITE_DEBUG: string
435
+ readonly VITE_AUTH0_DOMAIN?: string
436
+ readonly VITE_AUTH0_CLIENT_ID?: string
437
+ }
438
+
439
+ interface ImportMeta {
440
+ readonly env: ImportMetaEnv
441
+ }
442
+ ```
443
+
444
+ ---
445
+
446
+ ## 관련 문서
447
+
448
+ - [Best Practices](./best-practices.md)
449
+ - [Server Functions](../library/tanstack-start/server-functions.md)
450
+ - [Vite 환경 변수 공식 문서](https://vitejs.dev/guide/env-and-mode.html)