@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.
- package/dist/index.js +7 -1
- package/package.json +1 -1
- package/templates/.claude/agents/analyst.md +5 -0
- package/templates/.claude/agents/architect.md +5 -0
- package/templates/.claude/agents/build-fixer.md +1 -0
- package/templates/.claude/agents/code-reviewer.md +1 -0
- package/templates/.claude/agents/critic.md +4 -0
- package/templates/.claude/agents/deep-executor.md +1 -0
- package/templates/.claude/agents/dependency-manager.md +2 -0
- package/templates/.claude/agents/deployment-validator.md +2 -0
- package/templates/.claude/agents/designer.md +2 -0
- package/templates/.claude/agents/document-writer.md +3 -0
- package/templates/.claude/agents/explore.md +1 -0
- package/templates/.claude/agents/git-operator.md +2 -0
- package/templates/.claude/agents/implementation-executor.md +2 -0
- package/templates/.claude/agents/ko-to-en-translator.md +3 -0
- package/templates/.claude/agents/lint-fixer.md +2 -0
- package/templates/.claude/agents/planner.md +3 -0
- package/templates/.claude/agents/pm.md +349 -0
- package/templates/.claude/agents/qa-tester.md +1 -0
- package/templates/.claude/agents/refactor-advisor.md +4 -0
- package/templates/.claude/agents/researcher.md +9 -1
- package/templates/.claude/agents/scientist.md +1 -0
- package/templates/.claude/agents/security-reviewer.md +1 -0
- package/templates/.claude/agents/tdd-guide.md +1 -0
- package/templates/.claude/agents/vision.md +1 -0
- package/templates/.claude/instructions/agent-patterns/agent-teams-usage.md +376 -0
- package/templates/.claude/instructions/sourcing/reliable-search.md +49 -2
- package/templates/.claude/scripts/agent-teams/check-availability.sh +238 -0
- package/templates/.claude/scripts/agent-teams/setup-tmux.sh +125 -0
- package/templates/.claude/skills/agent-teams-setup/SKILL.md +460 -0
- package/templates/.claude/skills/brainstorm/SKILL.md +1 -0
- package/templates/.claude/skills/bug-fix/SKILL.md +1 -0
- package/templates/.claude/skills/crawler/SKILL.md +2 -0
- package/templates/.claude/skills/docs-creator/SKILL.md +1 -0
- package/templates/.claude/skills/docs-fetch/SKILL.md +6 -4
- package/templates/.claude/skills/docs-refactor/SKILL.md +1 -0
- package/templates/.claude/skills/elon-musk/SKILL.md +1 -0
- package/templates/.claude/skills/execute/SKILL.md +1 -0
- package/templates/.claude/skills/feedback/SKILL.md +1 -0
- package/templates/.claude/skills/figma-to-code/SKILL.md +1 -0
- package/templates/.claude/skills/genius-thinking/SKILL.md +1 -0
- package/templates/.claude/skills/global-uiux-design/SKILL.md +1 -0
- package/templates/.claude/skills/korea-uiux-design/SKILL.md +1 -0
- package/templates/.claude/skills/nextjs-react-best-practices/SKILL.md +1 -0
- package/templates/.claude/skills/plan/SKILL.md +1 -0
- package/templates/.claude/skills/prd/SKILL.md +1 -0
- package/templates/.claude/skills/project-optimizer/AGENTS.md +275 -0
- package/templates/.claude/skills/project-optimizer/SKILL.md +375 -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/ralph/SKILL.md +1 -0
- package/templates/.claude/skills/refactor/SKILL.md +1 -0
- package/templates/.claude/skills/research/SKILL.md +1 -0
- package/templates/.claude/skills/sql-optimizer/SKILL.md +438 -0
- package/templates/.claude/skills/sql-optimizer/orm-patterns.md +218 -0
- package/templates/.claude/skills/startup-validator/SKILL.md +1 -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 +94 -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 +571 -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
|
@@ -1,59 +1,825 @@
|
|
|
1
1
|
# TanStack Start - Routing
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> File-based routing with TanStack Router (v1.159.4)
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<overview>
|
|
8
|
+
|
|
9
|
+
## File-based Routing
|
|
10
|
+
|
|
11
|
+
TanStack Start는 TanStack Router 위에 구축되어 있으며, Router의 모든 기능을 사용할 수 있습니다. 파일 시스템 구조로 라우팅이 자동 생성됩니다.
|
|
12
|
+
|
|
13
|
+
| 개념 | 설명 |
|
|
14
|
+
|------|------|
|
|
15
|
+
| **File-based** | 파일 경로 = URL 경로 |
|
|
16
|
+
| **Dynamic Routes** | `$param.tsx` = `/:param` |
|
|
17
|
+
| **Nested Routes** | 폴더 구조 = 계층 라우트 |
|
|
18
|
+
| **Server Routes** | API 엔드포인트 (Server Functions 대신) |
|
|
19
|
+
| **Loader** | 라우트 로드 시 데이터 사전 로드 |
|
|
20
|
+
| **SSR** | 서버 사이드 렌더링 옵션 (true/false/'data-only') |
|
|
21
|
+
| **Pathless Layout** | URL 경로 없이 레이아웃/로직 적용 |
|
|
22
|
+
| **Non-Nested** | 부모 경로에서 언네스팅 |
|
|
23
|
+
| **Grouped** | 디렉토리 그룹핑 (경로 계층 영향 없음) |
|
|
24
|
+
|
|
25
|
+
</overview>
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
<router_config>
|
|
30
|
+
|
|
31
|
+
## Router 설정
|
|
32
|
+
|
|
33
|
+
`src/router.tsx` 파일에서 라우터 동작을 설정합니다.
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// src/router.tsx
|
|
37
|
+
import { createRouter } from '@tanstack/react-router'
|
|
38
|
+
import { routeTree } from './routeTree.gen'
|
|
39
|
+
|
|
40
|
+
// 매번 새 router 인스턴스를 반환하는 함수 export
|
|
41
|
+
export function getRouter() {
|
|
42
|
+
const router = createRouter({
|
|
43
|
+
routeTree,
|
|
44
|
+
scrollRestoration: true,
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
return router
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
> **참고:** `routeTree.gen.ts`는 `npm run dev` 또는 `npm run start` 실행 시 자동 생성됩니다. TanStack Start의 타입 안전성은 이 파일에 의존합니다.
|
|
52
|
+
|
|
53
|
+
</router_config>
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
<file_structure>
|
|
58
|
+
|
|
59
|
+
## 파일 구조 -> URL 매핑
|
|
60
|
+
|
|
61
|
+
| 파일 경로 | URL | 타입 |
|
|
62
|
+
|----------|-----|------|
|
|
63
|
+
| `routes/index.tsx` | `/` | Index Route |
|
|
64
|
+
| `routes/about.tsx` | `/about` | Static Route |
|
|
65
|
+
| `routes/posts.tsx` | `/posts` | "Layout" Route |
|
|
66
|
+
| `routes/posts/index.tsx` | `/posts/` | Index Route |
|
|
67
|
+
| `routes/posts/$postId.tsx` | `/posts/:postId` | Dynamic Route |
|
|
68
|
+
| `routes/rest/$.tsx` | `/rest/*` | Wildcard Route |
|
|
69
|
+
| `routes/__root.tsx` | - | Root layout (모든 라우트 부모) |
|
|
70
|
+
| `routes/_authed.tsx` | - | Pathless Layout (인증 레이아웃 등) |
|
|
71
|
+
| `routes/dashboard/_layout.tsx` | - | 중첩 레이아웃 |
|
|
72
|
+
|
|
73
|
+
### 라우트 타입 정리
|
|
74
|
+
|
|
75
|
+
| 타입 | 설명 | 예시 |
|
|
76
|
+
|------|------|------|
|
|
77
|
+
| **Index Routes** | URL이 정확히 일치 | `index.tsx` |
|
|
78
|
+
| **Dynamic Routes** | URL 일부를 변수로 캡처 | `$postId.tsx` |
|
|
79
|
+
| **Wildcard/Splat** | URL 전체를 캡처 | `$.tsx` |
|
|
80
|
+
| **Pathless Layout** | URL 없이 레이아웃 적용 | `_authed.tsx` |
|
|
81
|
+
| **Non-Nested** | 부모에서 독립 | 별도 컴포넌트 트리 |
|
|
82
|
+
| **Grouped** | 디렉토리 정리용 | 경로 계층 영향 없음 |
|
|
83
|
+
|
|
84
|
+
</file_structure>
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
<root_route>
|
|
89
|
+
|
|
90
|
+
## Root Route (__root.tsx)
|
|
91
|
+
|
|
92
|
+
Root Route는 라우트 트리 최상위이며, 모든 라우트를 감쌉니다.
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
// src/routes/__root.tsx
|
|
96
|
+
import type { ReactNode } from 'react'
|
|
97
|
+
import {
|
|
98
|
+
Outlet,
|
|
99
|
+
createRootRoute,
|
|
100
|
+
HeadContent,
|
|
101
|
+
Scripts,
|
|
102
|
+
} from '@tanstack/react-router'
|
|
103
|
+
|
|
104
|
+
export const Route = createRootRoute({
|
|
105
|
+
head: () => ({
|
|
106
|
+
meta: [
|
|
107
|
+
{ charSet: 'utf-8' },
|
|
108
|
+
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
|
109
|
+
{ title: 'TanStack Start Starter' },
|
|
110
|
+
],
|
|
111
|
+
}),
|
|
112
|
+
component: RootComponent,
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
function RootComponent() {
|
|
116
|
+
return (
|
|
117
|
+
<RootDocument>
|
|
118
|
+
<Outlet />
|
|
119
|
+
</RootDocument>
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function RootDocument({ children }: Readonly<{ children: ReactNode }>) {
|
|
124
|
+
return (
|
|
125
|
+
<html>
|
|
126
|
+
<head>
|
|
127
|
+
<HeadContent />
|
|
128
|
+
</head>
|
|
129
|
+
<body>
|
|
130
|
+
{children}
|
|
131
|
+
<Scripts />
|
|
132
|
+
</body>
|
|
133
|
+
</html>
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**특징:**
|
|
139
|
+
- 경로 없음 - **항상** 매칭
|
|
140
|
+
- 컴포넌트가 **항상** 렌더링
|
|
141
|
+
- document shell (`<html>`, `<body>`) 렌더링 장소
|
|
142
|
+
- 전역 로직 처리에 적합
|
|
143
|
+
|
|
144
|
+
**핵심 컴포넌트:**
|
|
145
|
+
| 컴포넌트 | 위치 | 역할 |
|
|
146
|
+
|---------|------|------|
|
|
147
|
+
| `<HeadContent />` | `<head>` 안 | head/title/meta/link/script 렌더링 |
|
|
148
|
+
| `<Outlet />` | 어디든 | 다음 매칭 자식 라우트 렌더링 |
|
|
149
|
+
| `<Scripts />` | `<body>` 안 | 클라이언트 JavaScript 로드 (필수) |
|
|
150
|
+
|
|
151
|
+
</root_route>
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
<basic_routes>
|
|
156
|
+
|
|
157
|
+
## 기본 라우트
|
|
158
|
+
|
|
159
|
+
### 정적 라우트
|
|
4
160
|
|
|
5
161
|
```tsx
|
|
6
|
-
//
|
|
7
|
-
|
|
162
|
+
// routes/about.tsx
|
|
163
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
164
|
+
|
|
165
|
+
export const Route = createFileRoute('/about')({
|
|
166
|
+
component: AboutPage,
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
const AboutPage = (): JSX.Element => {
|
|
170
|
+
return (
|
|
171
|
+
<div>
|
|
172
|
+
<h1>About</h1>
|
|
173
|
+
<p>This is the about page</p>
|
|
174
|
+
</div>
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### 인덱스 라우트
|
|
8
180
|
|
|
9
|
-
|
|
181
|
+
```tsx
|
|
182
|
+
// routes/index.tsx
|
|
10
183
|
export const Route = createFileRoute('/')({
|
|
11
|
-
component:
|
|
12
|
-
|
|
184
|
+
component: HomePage,
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
const HomePage = (): JSX.Element => {
|
|
188
|
+
return <h1>Welcome Home</h1>
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
> **참고:** `createFileRoute`에 전달하는 경로 문자열은 TanStack Router Bundler Plugin이 **자동으로 작성/관리**합니다. 파일 이동이나 이름 변경 시 자동 업데이트됩니다.
|
|
193
|
+
|
|
194
|
+
</basic_routes>
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
<nested_routing>
|
|
199
|
+
|
|
200
|
+
## 중첩 라우팅 (Nested Routing)
|
|
201
|
+
|
|
202
|
+
TanStack Router는 URL을 올바른 컴포넌트 트리에 매칭합니다.
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
routes/
|
|
206
|
+
├── __root.tsx # <Root> 렌더링
|
|
207
|
+
├── posts.tsx # <Posts> 렌더링
|
|
208
|
+
├── posts/$postId.tsx # <Post> 렌더링
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
URL: `/posts/123` -> 컴포넌트 트리:
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
<Root>
|
|
215
|
+
<Posts>
|
|
216
|
+
<Post />
|
|
217
|
+
</Posts>
|
|
218
|
+
</Root>
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
</nested_routing>
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
<loaders>
|
|
226
|
+
|
|
227
|
+
## Loader: 데이터 사전 로드
|
|
228
|
+
|
|
229
|
+
### 기본 Loader
|
|
230
|
+
|
|
231
|
+
```tsx
|
|
232
|
+
// routes/posts/index.tsx
|
|
233
|
+
export const Route = createFileRoute('/posts')({
|
|
234
|
+
loader: async (): Promise<{ posts: Post[] }> => {
|
|
235
|
+
const posts = await getPosts()
|
|
236
|
+
return { posts }
|
|
237
|
+
},
|
|
238
|
+
component: PostsPage,
|
|
13
239
|
})
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
240
|
+
|
|
241
|
+
const PostsPage = (): JSX.Element => {
|
|
242
|
+
const { posts } = Route.useLoaderData()
|
|
243
|
+
|
|
244
|
+
return (
|
|
245
|
+
<ul>
|
|
246
|
+
{posts.map((post) => (
|
|
247
|
+
<li key={post.id}>
|
|
248
|
+
<h2>{post.title}</h2>
|
|
249
|
+
</li>
|
|
250
|
+
))}
|
|
251
|
+
</ul>
|
|
252
|
+
)
|
|
17
253
|
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Server Function으로 데이터 로드
|
|
257
|
+
|
|
258
|
+
```tsx
|
|
259
|
+
export const Route = createFileRoute('/dashboard')({
|
|
260
|
+
loader: async (): Promise<{ dashboard: DashboardData }> => {
|
|
261
|
+
const dashboard = await getDashboardData()
|
|
262
|
+
return { dashboard }
|
|
263
|
+
},
|
|
264
|
+
component: DashboardPage,
|
|
265
|
+
})
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### 로더에서 에러 처리
|
|
269
|
+
|
|
270
|
+
```tsx
|
|
271
|
+
export const Route = createFileRoute('/items/$id')({
|
|
272
|
+
loader: async ({ params }): Promise<{ item: Item }> => {
|
|
273
|
+
try {
|
|
274
|
+
const item = await getItemById(params.id)
|
|
275
|
+
if (!item) {
|
|
276
|
+
throw new Error('Item not found')
|
|
277
|
+
}
|
|
278
|
+
return { item }
|
|
279
|
+
} catch (error) {
|
|
280
|
+
throw redirect({ to: '/items' })
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
component: ItemDetailPage,
|
|
284
|
+
})
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
</loaders>
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
<dynamic_routes>
|
|
18
292
|
|
|
19
|
-
|
|
293
|
+
## 동적 라우트 ($param)
|
|
294
|
+
|
|
295
|
+
### 단일 파라미터
|
|
296
|
+
|
|
297
|
+
```tsx
|
|
298
|
+
// routes/users/$id.tsx
|
|
20
299
|
export const Route = createFileRoute('/users/$id')({
|
|
21
|
-
loader: async ({ params })
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
300
|
+
loader: async ({ params }): Promise<{ user: User }> => {
|
|
301
|
+
const user = await getUserById(params.id)
|
|
302
|
+
if (!user) {
|
|
303
|
+
throw redirect({ to: '/users' })
|
|
304
|
+
}
|
|
305
|
+
return { user }
|
|
306
|
+
},
|
|
307
|
+
component: UserDetailPage,
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
const UserDetailPage = (): JSX.Element => {
|
|
311
|
+
const { user } = Route.useLoaderData()
|
|
312
|
+
const { id } = Route.useParams()
|
|
313
|
+
|
|
314
|
+
return (
|
|
315
|
+
<div>
|
|
316
|
+
<h1>{user.name}</h1>
|
|
317
|
+
<p>ID: {id}</p>
|
|
318
|
+
</div>
|
|
319
|
+
)
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### 다중 파라미터
|
|
324
|
+
|
|
325
|
+
```tsx
|
|
326
|
+
// routes/blog/$year/$month/$day.tsx
|
|
327
|
+
export const Route = createFileRoute('/blog/$year/$month/$day')({
|
|
328
|
+
loader: async ({ params }): Promise<{ posts: Post[] }> => {
|
|
329
|
+
const posts = await getPostsByDate(params.year, params.month, params.day)
|
|
330
|
+
return { posts }
|
|
331
|
+
},
|
|
332
|
+
component: BlogArchivePage,
|
|
333
|
+
})
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Catch-all ($.tsx)
|
|
337
|
+
|
|
338
|
+
```tsx
|
|
339
|
+
// routes/search/$.tsx
|
|
340
|
+
export const Route = createFileRoute('/search/$')({
|
|
341
|
+
loader: async ({ params }): Promise<{ query: string }> => {
|
|
342
|
+
return { query: params._ }
|
|
343
|
+
},
|
|
344
|
+
component: SearchPage,
|
|
345
|
+
})
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
</dynamic_routes>
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
<ssr_options>
|
|
353
|
+
|
|
354
|
+
## SSR (Server-Side Rendering) 옵션
|
|
355
|
+
|
|
356
|
+
### Selective SSR
|
|
357
|
+
|
|
358
|
+
`ssr` 속성으로 라우트별 SSR 동작을 제어합니다. 기본값은 `true`입니다.
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
// src/start.ts에서 전역 기본값 변경 가능
|
|
362
|
+
import { createStart } from '@tanstack/react-start'
|
|
363
|
+
|
|
364
|
+
export const startInstance = createStart(() => ({
|
|
365
|
+
defaultSsr: false, // 기본적으로 SSR 비활성화
|
|
366
|
+
}))
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### ssr: true (기본값)
|
|
370
|
+
|
|
371
|
+
초기 요청 시:
|
|
372
|
+
1. `beforeLoad` 서버에서 실행, context를 클라이언트에 전송
|
|
373
|
+
2. `loader` 서버에서 실행, 데이터를 클라이언트에 전송
|
|
374
|
+
3. 컴포넌트 서버에서 렌더링, HTML을 클라이언트에 전송
|
|
375
|
+
|
|
376
|
+
```tsx
|
|
377
|
+
export const Route = createFileRoute('/products')({
|
|
378
|
+
ssr: true, // 기본값 (생략 가능)
|
|
379
|
+
loader: async () => {
|
|
380
|
+
const products = await getProducts()
|
|
381
|
+
return { products }
|
|
382
|
+
},
|
|
383
|
+
component: ProductsPage,
|
|
384
|
+
})
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### ssr: false
|
|
388
|
+
|
|
389
|
+
서버 사이드 `beforeLoad`/`loader` 실행 및 컴포넌트 렌더링 비활성화.
|
|
390
|
+
|
|
391
|
+
```tsx
|
|
392
|
+
export const Route = createFileRoute('/dashboard')({
|
|
393
|
+
ssr: false, // 클라이언트에서만 렌더링
|
|
394
|
+
beforeLoad: () => {
|
|
395
|
+
console.log('클라이언트 hydration 중에만 실행')
|
|
25
396
|
},
|
|
397
|
+
component: DashboardPage,
|
|
26
398
|
})
|
|
399
|
+
// 인증 필요한 페이지, 브라우저 전용 API 사용 시 적합
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### ssr: 'data-only'
|
|
403
|
+
|
|
404
|
+
`beforeLoad`와 `loader`는 서버에서 실행하지만, 컴포넌트 렌더링은 클라이언트에서만.
|
|
405
|
+
|
|
406
|
+
```tsx
|
|
407
|
+
export const Route = createFileRoute('/items')({
|
|
408
|
+
ssr: 'data-only',
|
|
409
|
+
loader: async () => {
|
|
410
|
+
// 서버에서 실행 (DB 접근 가능)
|
|
411
|
+
const items = await getItems()
|
|
412
|
+
return { items }
|
|
413
|
+
},
|
|
414
|
+
component: ItemsPage,
|
|
415
|
+
})
|
|
416
|
+
// 무거운 컴포넌트 + 서버 데이터 조합에 적합
|
|
417
|
+
```
|
|
27
418
|
|
|
28
|
-
|
|
29
|
-
ssr: true // 전체 SSR (기본값)
|
|
30
|
-
ssr: false // 클라이언트만
|
|
31
|
-
ssr: 'data-only' // 데이터만 서버
|
|
419
|
+
</ssr_options>
|
|
32
420
|
|
|
33
|
-
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
<before_load>
|
|
424
|
+
|
|
425
|
+
## beforeLoad: 라우트 진입 전 검증
|
|
426
|
+
|
|
427
|
+
### 인증 체크
|
|
428
|
+
|
|
429
|
+
```tsx
|
|
430
|
+
export const Route = createFileRoute('/dashboard')({
|
|
431
|
+
beforeLoad: async ({ location }): Promise<{ user: User }> => {
|
|
432
|
+
const user = await getCurrentUser()
|
|
433
|
+
|
|
434
|
+
if (!user) {
|
|
435
|
+
throw redirect({
|
|
436
|
+
to: '/login',
|
|
437
|
+
search: { redirect: location.href },
|
|
438
|
+
})
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return { user }
|
|
442
|
+
},
|
|
443
|
+
component: DashboardPage,
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
const DashboardPage = (): JSX.Element => {
|
|
447
|
+
const { user } = Route.useRouteContext()
|
|
448
|
+
return <h1>Welcome, {user.name}!</h1>
|
|
449
|
+
}
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### 권한 체크
|
|
453
|
+
|
|
454
|
+
```tsx
|
|
455
|
+
export const Route = createFileRoute('/admin')({
|
|
456
|
+
beforeLoad: async ({ context }): Promise<{ user: User }> => {
|
|
457
|
+
const user = await getCurrentUser()
|
|
458
|
+
|
|
459
|
+
if (!user || user.role !== 'ADMIN') {
|
|
460
|
+
throw redirect({ to: '/' })
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return { user }
|
|
464
|
+
},
|
|
465
|
+
component: AdminPage,
|
|
466
|
+
})
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
### 데이터 검증
|
|
470
|
+
|
|
471
|
+
```tsx
|
|
472
|
+
export const Route = createFileRoute('/items/$id')({
|
|
473
|
+
beforeLoad: async ({ params }): Promise<{ valid: boolean }> => {
|
|
474
|
+
const isValidId = await validateItemId(params.id)
|
|
475
|
+
|
|
476
|
+
if (!isValidId) {
|
|
477
|
+
throw redirect({ to: '/items' })
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return { valid: true }
|
|
481
|
+
},
|
|
482
|
+
loader: async ({ params }) => {
|
|
483
|
+
// beforeLoad가 성공한 후 실행
|
|
484
|
+
return { item: await getItemById(params.id) }
|
|
485
|
+
},
|
|
486
|
+
component: ItemDetailPage,
|
|
487
|
+
})
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
</before_load>
|
|
491
|
+
|
|
492
|
+
---
|
|
493
|
+
|
|
494
|
+
<server_routes>
|
|
495
|
+
|
|
496
|
+
## Server Routes: API 엔드포인트
|
|
497
|
+
|
|
498
|
+
**주의:** Server Functions 사용 권장. Server Routes는 복잡한 요청/응답이 필요할 때만 사용.
|
|
499
|
+
|
|
500
|
+
### 기본 Server Route
|
|
501
|
+
|
|
502
|
+
```tsx
|
|
503
|
+
// routes/api/hello.tsx
|
|
34
504
|
export const Route = createFileRoute('/api/hello')({
|
|
35
505
|
server: {
|
|
36
506
|
handlers: {
|
|
37
|
-
GET: async () =>
|
|
38
|
-
|
|
507
|
+
GET: async (): Promise<Response> => {
|
|
508
|
+
return new Response('Hello World', {
|
|
509
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
510
|
+
})
|
|
511
|
+
},
|
|
512
|
+
},
|
|
513
|
+
},
|
|
514
|
+
})
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
### 여러 메서드
|
|
518
|
+
|
|
519
|
+
```tsx
|
|
520
|
+
export const Route = createFileRoute('/api/users')({
|
|
521
|
+
server: {
|
|
522
|
+
handlers: {
|
|
523
|
+
GET: async (): Promise<Response> => {
|
|
524
|
+
const users = await prisma.user.findMany()
|
|
525
|
+
return Response.json(users)
|
|
526
|
+
},
|
|
527
|
+
POST: async ({ request }): Promise<Response> => {
|
|
39
528
|
const body = await request.json()
|
|
40
|
-
|
|
529
|
+
const user = await prisma.user.create({ data: body })
|
|
530
|
+
return Response.json(user, { status: 201 })
|
|
531
|
+
},
|
|
532
|
+
},
|
|
533
|
+
},
|
|
534
|
+
})
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### 미들웨어 + Server Route
|
|
538
|
+
|
|
539
|
+
```tsx
|
|
540
|
+
export const Route = createFileRoute('/api/protected')({
|
|
541
|
+
server: {
|
|
542
|
+
middleware: [authMiddleware, adminMiddleware],
|
|
543
|
+
handlers: {
|
|
544
|
+
GET: async ({ request }): Promise<Response> => {
|
|
545
|
+
return Response.json({ data: 'admin only' })
|
|
41
546
|
},
|
|
42
547
|
},
|
|
43
548
|
},
|
|
44
549
|
})
|
|
45
550
|
```
|
|
46
551
|
|
|
47
|
-
</
|
|
552
|
+
</server_routes>
|
|
553
|
+
|
|
554
|
+
---
|
|
555
|
+
|
|
556
|
+
<catch_all>
|
|
557
|
+
|
|
558
|
+
## Catch-all Route (404)
|
|
559
|
+
|
|
560
|
+
```tsx
|
|
561
|
+
// routes/$.tsx
|
|
562
|
+
export const Route = createFileRoute('/$')({
|
|
563
|
+
component: NotFoundPage,
|
|
564
|
+
})
|
|
48
565
|
|
|
49
|
-
|
|
566
|
+
const NotFoundPage = (): JSX.Element => {
|
|
567
|
+
const { _splat } = Route.useParams()
|
|
50
568
|
|
|
569
|
+
return (
|
|
570
|
+
<div style={{ textAlign: 'center', padding: '2rem' }}>
|
|
571
|
+
<h1>404 - Page Not Found</h1>
|
|
572
|
+
<p>The page "{_splat}" does not exist.</p>
|
|
573
|
+
<a href="/">Go Home</a>
|
|
574
|
+
</div>
|
|
575
|
+
)
|
|
576
|
+
}
|
|
51
577
|
```
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
578
|
+
|
|
579
|
+
### Error Boundary
|
|
580
|
+
|
|
581
|
+
```tsx
|
|
582
|
+
// routes/__root.tsx
|
|
583
|
+
export const Route = createRootRoute({
|
|
584
|
+
component: RootLayout,
|
|
585
|
+
errorComponent: ({ error }) => (
|
|
586
|
+
<div>
|
|
587
|
+
<h1>Error</h1>
|
|
588
|
+
<p>{error.message}</p>
|
|
589
|
+
</div>
|
|
590
|
+
),
|
|
591
|
+
})
|
|
57
592
|
```
|
|
58
593
|
|
|
59
|
-
</
|
|
594
|
+
</catch_all>
|
|
595
|
+
|
|
596
|
+
---
|
|
597
|
+
|
|
598
|
+
<nested_layouts>
|
|
599
|
+
|
|
600
|
+
## 중첩 레이아웃
|
|
601
|
+
|
|
602
|
+
### Pathless Layout (_authed 패턴)
|
|
603
|
+
|
|
604
|
+
URL 경로에 영향 없이 레이아웃과 로직을 그룹에 적용:
|
|
605
|
+
|
|
606
|
+
```tsx
|
|
607
|
+
// routes/_authed.tsx (pathless layout - URL에 _authed 없음)
|
|
608
|
+
export const Route = createFileRoute('/_authed')({
|
|
609
|
+
beforeLoad: async ({ location }) => {
|
|
610
|
+
const user = await getCurrentUser()
|
|
611
|
+
if (!user) {
|
|
612
|
+
throw redirect({
|
|
613
|
+
to: '/login',
|
|
614
|
+
search: { redirect: location.href },
|
|
615
|
+
})
|
|
616
|
+
}
|
|
617
|
+
return { user }
|
|
618
|
+
},
|
|
619
|
+
component: AuthedLayout,
|
|
620
|
+
})
|
|
621
|
+
|
|
622
|
+
function AuthedLayout() {
|
|
623
|
+
return (
|
|
624
|
+
<div>
|
|
625
|
+
<Outlet />
|
|
626
|
+
</div>
|
|
627
|
+
)
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// routes/_authed/dashboard.tsx -> /dashboard
|
|
631
|
+
export const Route = createFileRoute('/_authed/dashboard')({
|
|
632
|
+
component: DashboardPage,
|
|
633
|
+
})
|
|
634
|
+
|
|
635
|
+
// routes/_authed/settings.tsx -> /settings
|
|
636
|
+
export const Route = createFileRoute('/_authed/settings')({
|
|
637
|
+
component: SettingsPage,
|
|
638
|
+
})
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
### 중첩 레이아웃 (Dashboard)
|
|
642
|
+
|
|
643
|
+
```tsx
|
|
644
|
+
// routes/dashboard/_layout.tsx
|
|
645
|
+
export const Route = createFileRoute('/dashboard')({
|
|
646
|
+
component: DashboardLayout,
|
|
647
|
+
loader: async () => {
|
|
648
|
+
const user = await getCurrentUser()
|
|
649
|
+
return { user }
|
|
650
|
+
},
|
|
651
|
+
})
|
|
652
|
+
|
|
653
|
+
const DashboardLayout = (): JSX.Element => {
|
|
654
|
+
const { user } = Route.useLoaderData()
|
|
655
|
+
|
|
656
|
+
return (
|
|
657
|
+
<div style={{ display: 'flex' }}>
|
|
658
|
+
<aside>
|
|
659
|
+
<p>Welcome, {user.name}</p>
|
|
660
|
+
</aside>
|
|
661
|
+
<main>
|
|
662
|
+
<Outlet />
|
|
663
|
+
</main>
|
|
664
|
+
</div>
|
|
665
|
+
)
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// routes/dashboard/index.tsx
|
|
669
|
+
export const Route = createFileRoute('/dashboard/')({
|
|
670
|
+
component: () => <h1>Dashboard Home</h1>,
|
|
671
|
+
})
|
|
672
|
+
|
|
673
|
+
// routes/dashboard/analytics.tsx
|
|
674
|
+
export const Route = createFileRoute('/dashboard/analytics')({
|
|
675
|
+
component: () => <h1>Analytics</h1>,
|
|
676
|
+
})
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
### Context 전달
|
|
680
|
+
|
|
681
|
+
```tsx
|
|
682
|
+
// 부모 라우트의 loader/beforeLoad에서 반환한 데이터는
|
|
683
|
+
// 자식 라우트에서 useRouteContext()로 접근 가능
|
|
684
|
+
const DashboardPage = (): JSX.Element => {
|
|
685
|
+
const { user } = Route.useRouteContext()
|
|
686
|
+
return <h1>Welcome, {user.name}!</h1>
|
|
687
|
+
}
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
</nested_layouts>
|
|
691
|
+
|
|
692
|
+
---
|
|
693
|
+
|
|
694
|
+
<search_params>
|
|
695
|
+
|
|
696
|
+
## Search Parameters (Query String)
|
|
697
|
+
|
|
698
|
+
```tsx
|
|
699
|
+
// routes/search.tsx
|
|
700
|
+
export const Route = createFileRoute('/search')({
|
|
701
|
+
validateSearch: (search): { q: string; page: number } => {
|
|
702
|
+
return {
|
|
703
|
+
q: (search.q as string) || '',
|
|
704
|
+
page: (search.page as number) || 1,
|
|
705
|
+
}
|
|
706
|
+
},
|
|
707
|
+
loader: async ({ search }) => {
|
|
708
|
+
const results = await searchItems(search.q, search.page)
|
|
709
|
+
return { results }
|
|
710
|
+
},
|
|
711
|
+
component: SearchPage,
|
|
712
|
+
})
|
|
713
|
+
|
|
714
|
+
const SearchPage = (): JSX.Element => {
|
|
715
|
+
const navigate = useNavigate({ from: '/search' })
|
|
716
|
+
const search = Route.useSearch()
|
|
717
|
+
const { results } = Route.useLoaderData()
|
|
718
|
+
|
|
719
|
+
return (
|
|
720
|
+
<div>
|
|
721
|
+
<input
|
|
722
|
+
value={search.q}
|
|
723
|
+
onChange={(e) => {
|
|
724
|
+
navigate({
|
|
725
|
+
search: { q: e.target.value, page: 1 },
|
|
726
|
+
})
|
|
727
|
+
}}
|
|
728
|
+
/>
|
|
729
|
+
<div>Page: {search.page}</div>
|
|
730
|
+
</div>
|
|
731
|
+
)
|
|
732
|
+
}
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
</search_params>
|
|
736
|
+
|
|
737
|
+
---
|
|
738
|
+
|
|
739
|
+
<navigation>
|
|
740
|
+
|
|
741
|
+
## 네비게이션
|
|
742
|
+
|
|
743
|
+
```tsx
|
|
744
|
+
import { Link, useNavigate } from '@tanstack/react-router'
|
|
745
|
+
|
|
746
|
+
// Link 컴포넌트
|
|
747
|
+
const HomePage = (): JSX.Element => {
|
|
748
|
+
return (
|
|
749
|
+
<div>
|
|
750
|
+
<Link to="/about">About</Link>
|
|
751
|
+
<Link to="/users/$id" params={{ id: '123' }}>
|
|
752
|
+
User Detail
|
|
753
|
+
</Link>
|
|
754
|
+
</div>
|
|
755
|
+
)
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// useNavigate 훅
|
|
759
|
+
const LoginPage = (): JSX.Element => {
|
|
760
|
+
const navigate = useNavigate()
|
|
761
|
+
|
|
762
|
+
const handleLogin = () => {
|
|
763
|
+
navigate({ to: '/dashboard' })
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
return <button onClick={handleLogin}>Login</button>
|
|
767
|
+
}
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
</navigation>
|
|
771
|
+
|
|
772
|
+
---
|
|
773
|
+
|
|
774
|
+
<project_structure>
|
|
775
|
+
|
|
776
|
+
## 권장 프로젝트 구조
|
|
777
|
+
|
|
778
|
+
```
|
|
779
|
+
src/
|
|
780
|
+
├── routes/
|
|
781
|
+
│ ├── __root.tsx # Root layout (html/body/scripts)
|
|
782
|
+
│ ├── index.tsx # /
|
|
783
|
+
│ ├── $.tsx # Catch-all (404)
|
|
784
|
+
│ ├── about.tsx # /about
|
|
785
|
+
│ ├── _authed.tsx # Pathless layout (인증 필요 페이지)
|
|
786
|
+
│ ├── _authed/
|
|
787
|
+
│ │ ├── dashboard.tsx # /dashboard (인증 필요)
|
|
788
|
+
│ │ └── settings.tsx # /settings (인증 필요)
|
|
789
|
+
│ ├── api/
|
|
790
|
+
│ │ └── hello.tsx # /api/hello (Server Routes)
|
|
791
|
+
│ ├── users/
|
|
792
|
+
│ │ ├── index.tsx # /users
|
|
793
|
+
│ │ ├── $id.tsx # /users/:id
|
|
794
|
+
│ │ └── -components/ # 페이지 전용
|
|
795
|
+
│ └── dashboard/
|
|
796
|
+
│ ├── _layout.tsx # 레이아웃
|
|
797
|
+
│ ├── index.tsx # /dashboard
|
|
798
|
+
│ └── analytics.tsx # /dashboard/analytics
|
|
799
|
+
├── router.tsx # Router 설정
|
|
800
|
+
├── routeTree.gen.ts # 자동 생성
|
|
801
|
+
├── functions/ # 공통 Server Functions
|
|
802
|
+
├── middleware/ # 공통 Middleware
|
|
803
|
+
└── components/
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
</project_structure>
|
|
807
|
+
|
|
808
|
+
---
|
|
809
|
+
|
|
810
|
+
<version_info>
|
|
811
|
+
|
|
812
|
+
**Version:** TanStack Start/Router v1.159.4
|
|
813
|
+
|
|
814
|
+
**Key Features:**
|
|
815
|
+
- File-based routing (zero-config, 자동 경로 관리)
|
|
816
|
+
- Type-safe route parameters (routeTree.gen.ts 기반)
|
|
817
|
+
- Selective SSR (true/false/'data-only', 라우트별 설정)
|
|
818
|
+
- createStart에서 defaultSsr 전역 설정
|
|
819
|
+
- beforeLoad for auth/validation
|
|
820
|
+
- Server Routes for API endpoints (메서드별 미들웨어 가능)
|
|
821
|
+
- Nested layouts, Pathless layouts, Grouped routes
|
|
822
|
+
- HeadContent/Scripts 컴포넌트 (Root Route 필수)
|
|
823
|
+
- scrollRestoration 기본 지원
|
|
824
|
+
|
|
825
|
+
</version_info>
|