@kood/claude-code 0.5.3 → 0.5.5
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 +552 -340
- package/package.json +1 -1
- package/templates/.claude/agents/document-writer.md +73 -306
- package/templates/.claude/instructions/agent-patterns/index.md +7 -7
- package/templates/.claude/instructions/document-templates/ralph-templates.md +71 -0
- package/templates/.claude/instructions/index.md +14 -14
- package/templates/.claude/instructions/multi-agent/agent-roster.md +14 -14
- package/templates/.claude/instructions/multi-agent/index.md +4 -4
- package/templates/.claude/skills/docs-creator/AGENTS.md +54 -176
- package/templates/.claude/skills/docs-creator/SKILL.md +98 -464
- package/templates/.claude/skills/docs-refactor/AGENTS.md +61 -190
- package/templates/.claude/skills/docs-refactor/SKILL.md +67 -443
- package/templates/.claude/skills/execute/SKILL.md +540 -13
- package/templates/.claude/skills/plan/SKILL.md +84 -18
- package/templates/.claude/skills/ralph/SKILL.md +17 -14
- package/templates/.claude/skills/refactor/AGENTS.md +269 -0
- package/templates/.claude/skills/refactor/SKILL.md +424 -66
- package/templates/.claude/skills/stitch-design/README.md +34 -0
- package/templates/.claude/skills/stitch-design/SKILL.md +213 -0
- package/templates/.claude/skills/stitch-design/examples/DESIGN.md +154 -0
- package/templates/.claude/skills/stitch-loop/README.md +54 -0
- package/templates/.claude/skills/stitch-loop/SKILL.md +316 -0
- package/templates/.claude/skills/stitch-loop/examples/SITE.md +73 -0
- package/templates/.claude/skills/stitch-loop/examples/next-prompt.md +25 -0
- package/templates/.claude/skills/stitch-loop/resources/baton-schema.md +61 -0
- package/templates/.claude/skills/stitch-loop/resources/site-template.md +104 -0
- package/templates/.claude/skills/stitch-react/README.md +36 -0
- package/templates/.claude/skills/stitch-react/SKILL.md +323 -0
- package/templates/.claude/skills/stitch-react/examples/gold-standard-card.tsx +88 -0
- package/templates/.claude/skills/stitch-react/package-lock.json +231 -0
- package/templates/.claude/skills/stitch-react/package.json +16 -0
- package/templates/.claude/skills/stitch-react/resources/architecture-checklist.md +15 -0
- package/templates/.claude/skills/stitch-react/resources/component-template.tsx +37 -0
- package/templates/.claude/skills/stitch-react/resources/stitch-api-reference.md +14 -0
- package/templates/.claude/skills/stitch-react/resources/style-guide.json +24 -0
- package/templates/.claude/skills/stitch-react/scripts/fetch-stitch.sh +30 -0
- package/templates/.claude/skills/stitch-react/scripts/validate.js +77 -0
- package/templates/hono/CLAUDE.md +28 -28
- package/templates/hono/docs/architecture.md +24 -24
- package/templates/hono/docs/deployment/cloudflare.md +18 -18
- package/templates/hono/docs/deployment/docker.md +13 -13
- package/templates/hono/docs/deployment/index.md +19 -19
- package/templates/hono/docs/deployment/railway.md +32 -32
- package/templates/hono/docs/deployment/vercel.md +29 -29
- package/templates/hono/docs/guides/conventions.md +57 -57
- package/templates/hono/docs/guides/env-setup.md +47 -47
- package/templates/hono/docs/guides/getting-started.md +27 -27
- package/templates/hono/docs/library/hono/error-handling.md +11 -11
- package/templates/hono/docs/library/hono/index.md +4 -4
- package/templates/hono/docs/library/hono/middleware.md +18 -18
- package/templates/hono/docs/library/hono/rpc.md +7 -7
- package/templates/hono/docs/library/hono/validation.md +6 -6
- package/templates/hono/docs/library/prisma/cloudflare-d1.md +29 -29
- package/templates/hono/docs/library/prisma/config.md +16 -16
- package/templates/hono/docs/library/prisma/index.md +32 -32
- package/templates/hono/docs/library/t3-env/index.md +22 -22
- package/templates/hono/docs/library/zod/index.md +31 -31
- package/templates/nextjs/CLAUDE.md +54 -54
- package/templates/nextjs/docs/architecture.md +146 -146
- package/templates/nextjs/docs/design.md +183 -183
- package/templates/nextjs/docs/guides/conventions.md +86 -86
- package/templates/nextjs/docs/guides/getting-started.md +28 -28
- package/templates/nextjs/docs/guides/routes.md +32 -32
- package/templates/nextjs/docs/library/better-auth/index.md +70 -70
- package/templates/nextjs/docs/library/nextjs/app-router.md +43 -43
- package/templates/nextjs/docs/library/nextjs/caching.md +73 -73
- package/templates/nextjs/docs/library/nextjs/index.md +51 -51
- package/templates/nextjs/docs/library/nextjs/middleware.md +41 -41
- package/templates/nextjs/docs/library/nextjs/route-handlers.md +31 -31
- package/templates/nextjs/docs/library/nextjs/server-actions.md +34 -34
- package/templates/nextjs/docs/library/prisma/cloudflare-d1.md +20 -20
- package/templates/nextjs/docs/library/prisma/config.md +18 -18
- package/templates/nextjs/docs/library/prisma/crud.md +17 -17
- package/templates/nextjs/docs/library/prisma/index.md +18 -18
- package/templates/nextjs/docs/library/prisma/relations.md +16 -16
- package/templates/nextjs/docs/library/prisma/schema.md +23 -23
- package/templates/nextjs/docs/library/prisma/setup.md +6 -6
- package/templates/nextjs/docs/library/prisma/transactions.md +10 -10
- package/templates/nextjs/docs/library/tanstack-query/index.md +6 -6
- package/templates/nextjs/docs/library/tanstack-query/invalidation.md +20 -20
- package/templates/nextjs/docs/library/tanstack-query/optimistic-updates.md +4 -4
- package/templates/nextjs/docs/library/tanstack-query/use-mutation.md +15 -15
- package/templates/nextjs/docs/library/tanstack-query/use-query.md +22 -22
- package/templates/nextjs/docs/library/zod/complex-types.md +11 -11
- package/templates/nextjs/docs/library/zod/index.md +8 -8
- package/templates/nextjs/docs/library/zod/transforms.md +11 -11
- package/templates/nextjs/docs/library/zod/validation.md +9 -9
- package/templates/npx/CLAUDE.md +38 -38
- package/templates/npx/docs/library/commander/index.md +12 -12
- package/templates/npx/docs/library/fs-extra/index.md +9 -9
- package/templates/npx/docs/library/prompts/index.md +3 -3
- package/templates/npx/docs/references/patterns.md +12 -12
- package/templates/tanstack-start/CLAUDE.md +54 -54
- package/templates/tanstack-start/docs/architecture.md +128 -128
- package/templates/tanstack-start/docs/design.md +169 -169
- package/templates/tanstack-start/docs/guides/conventions.md +43 -43
- package/templates/tanstack-start/docs/guides/env-setup.md +35 -35
- package/templates/tanstack-start/docs/guides/getting-started.md +19 -19
- package/templates/tanstack-start/docs/guides/hooks.md +45 -45
- package/templates/tanstack-start/docs/guides/routes.md +54 -54
- package/templates/tanstack-start/docs/guides/services.md +45 -45
- package/templates/tanstack-start/docs/library/prisma/cloudflare-d1.md +19 -19
- package/templates/tanstack-start/docs/library/prisma/config.md +16 -16
- package/templates/tanstack-start/docs/library/prisma/crud.md +17 -17
- package/templates/tanstack-start/docs/library/prisma/relations.md +16 -16
- package/templates/tanstack-start/docs/library/prisma/schema.md +23 -23
- package/templates/tanstack-start/docs/library/prisma/setup.md +6 -6
- package/templates/tanstack-start/docs/library/prisma/transactions.md +10 -10
- package/templates/tanstack-start/docs/library/tanstack-query/invalidation.md +19 -19
- package/templates/tanstack-start/docs/library/tanstack-query/optimistic-updates.md +4 -4
- package/templates/tanstack-start/docs/library/tanstack-query/use-mutation.md +14 -14
- package/templates/tanstack-start/docs/library/tanstack-query/use-query.md +21 -21
- package/templates/tanstack-start/docs/library/tanstack-router/error-handling.md +9 -9
- package/templates/tanstack-start/docs/library/tanstack-router/hooks.md +11 -11
- package/templates/tanstack-start/docs/library/tanstack-router/navigation.md +17 -17
- package/templates/tanstack-start/docs/library/tanstack-router/route-context.md +5 -5
- package/templates/tanstack-start/docs/library/tanstack-router/search-params.md +10 -10
- package/templates/tanstack-start/docs/library/tanstack-start/auth-patterns.md +8 -8
- package/templates/tanstack-start/docs/library/tanstack-start/middleware.md +9 -9
- package/templates/tanstack-start/docs/library/tanstack-start/routing.md +6 -6
- package/templates/tanstack-start/docs/library/tanstack-start/server-functions.md +18 -18
- package/templates/tanstack-start/docs/library/tanstack-start/setup.md +4 -4
- package/templates/tanstack-start/docs/library/zod/complex-types.md +11 -11
- package/templates/tanstack-start/docs/library/zod/transforms.md +11 -11
- package/templates/tanstack-start/docs/library/zod/validation.md +9 -9
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
# App Router
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> 파일 기반 라우팅 시스템
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## 파일 구조와 라우팅
|
|
8
8
|
|
|
9
|
-
###
|
|
9
|
+
### 기본 규칙
|
|
10
10
|
|
|
11
|
-
|
|
|
12
|
-
|
|
13
|
-
| `app/page.tsx` | `/` |
|
|
14
|
-
| `app/about/page.tsx` | `/about` | About
|
|
15
|
-
| `app/blog/[slug]/page.tsx` | `/blog/:slug` |
|
|
11
|
+
| 파일 | 라우트 | 설명 |
|
|
12
|
+
|------|--------|------|
|
|
13
|
+
| `app/page.tsx` | `/` | 홈 페이지 |
|
|
14
|
+
| `app/about/page.tsx` | `/about` | About 페이지 |
|
|
15
|
+
| `app/blog/[slug]/page.tsx` | `/blog/:slug` | 동적 라우트 |
|
|
16
16
|
| `app/shop/[...slug]/page.tsx` | `/shop/*` | Catch-all |
|
|
17
17
|
| `app/docs/[[...slug]]/page.tsx` | `/docs/*` | Optional catch-all |
|
|
18
18
|
|
|
19
|
-
###
|
|
19
|
+
### 예시
|
|
20
20
|
|
|
21
21
|
```typescript
|
|
22
22
|
// app/blog/[slug]/page.tsx
|
|
@@ -38,20 +38,20 @@ export default async function BlogPost({ params, searchParams }: PageProps) {
|
|
|
38
38
|
|
|
39
39
|
## Layouts
|
|
40
40
|
|
|
41
|
-
### Root Layout (
|
|
41
|
+
### Root Layout (필수)
|
|
42
42
|
|
|
43
43
|
```typescript
|
|
44
44
|
// app/layout.tsx
|
|
45
45
|
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
46
46
|
return (
|
|
47
|
-
<html lang="
|
|
47
|
+
<html lang="ko">
|
|
48
48
|
<body>{children}</body>
|
|
49
49
|
</html>
|
|
50
50
|
)
|
|
51
51
|
}
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
###
|
|
54
|
+
### 중첩 Layout
|
|
55
55
|
|
|
56
56
|
```typescript
|
|
57
57
|
// app/dashboard/layout.tsx
|
|
@@ -65,10 +65,10 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
|
|
|
65
65
|
}
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
-
|
|
70
|
-
-
|
|
71
|
-
-
|
|
68
|
+
**특징:**
|
|
69
|
+
- 중첩 가능 (부모 → 자식 순서)
|
|
70
|
+
- 리렌더링 없이 유지됨
|
|
71
|
+
- props로 `children` 받음
|
|
72
72
|
|
|
73
73
|
---
|
|
74
74
|
|
|
@@ -87,15 +87,15 @@ app/
|
|
|
87
87
|
└── page.tsx # /products
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
-
|
|
92
|
-
-
|
|
90
|
+
**용도:**
|
|
91
|
+
- URL에 영향 없이 폴더 그룹화
|
|
92
|
+
- 다른 레이아웃 적용
|
|
93
93
|
|
|
94
94
|
---
|
|
95
95
|
|
|
96
|
-
##
|
|
96
|
+
## 동적 라우트
|
|
97
97
|
|
|
98
|
-
###
|
|
98
|
+
### 단일 파라미터
|
|
99
99
|
|
|
100
100
|
```typescript
|
|
101
101
|
// app/posts/[id]/page.tsx
|
|
@@ -104,7 +104,7 @@ export default async function PostPage({ params }: { params: { id: string } }) {
|
|
|
104
104
|
return <article>{post.title}</article>
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
//
|
|
107
|
+
// 정적 생성 (빌드 시)
|
|
108
108
|
export async function generateStaticParams() {
|
|
109
109
|
const posts = await prisma.post.findMany()
|
|
110
110
|
return posts.map(post => ({ id: post.id }))
|
|
@@ -123,7 +123,7 @@ export default function DocsPage({ params }: { params: { slug: string[] } }) {
|
|
|
123
123
|
|
|
124
124
|
---
|
|
125
125
|
|
|
126
|
-
##
|
|
126
|
+
## 병렬 라우트
|
|
127
127
|
|
|
128
128
|
```
|
|
129
129
|
app/
|
|
@@ -157,7 +157,7 @@ export default function Layout({
|
|
|
157
157
|
|
|
158
158
|
---
|
|
159
159
|
|
|
160
|
-
##
|
|
160
|
+
## 인터셉팅 라우트
|
|
161
161
|
|
|
162
162
|
```
|
|
163
163
|
app/
|
|
@@ -172,11 +172,11 @@ app/
|
|
|
172
172
|
└── page.tsx
|
|
173
173
|
```
|
|
174
174
|
|
|
175
|
-
|
|
176
|
-
- `(.)` -
|
|
177
|
-
- `(..)` -
|
|
178
|
-
- `(..)(..)` -
|
|
179
|
-
- `(...)` -
|
|
175
|
+
**컨벤션:**
|
|
176
|
+
- `(.)` - 같은 레벨
|
|
177
|
+
- `(..)` - 한 단계 위
|
|
178
|
+
- `(..)(..)` - 두 단계 위
|
|
179
|
+
- `(...)` - 루트부터
|
|
180
180
|
|
|
181
181
|
---
|
|
182
182
|
|
|
@@ -203,15 +203,15 @@ export async function generateMetadata({ params }: { params: { slug: string } })
|
|
|
203
203
|
|
|
204
204
|
---
|
|
205
205
|
|
|
206
|
-
##
|
|
206
|
+
## 특수 파일
|
|
207
207
|
|
|
208
|
-
|
|
|
209
|
-
|
|
210
|
-
| `loading.tsx` | Suspense
|
|
208
|
+
| 파일 | 용도 |
|
|
209
|
+
|------|------|
|
|
210
|
+
| `loading.tsx` | Suspense 폴백 |
|
|
211
211
|
| `error.tsx` | Error Boundary |
|
|
212
|
-
| `not-found.tsx` | 404
|
|
213
|
-
| `template.tsx` |
|
|
214
|
-
| `default.tsx` |
|
|
212
|
+
| `not-found.tsx` | 404 페이지 |
|
|
213
|
+
| `template.tsx` | 리렌더링되는 Layout |
|
|
214
|
+
| `default.tsx` | 병렬 라우트 폴백 |
|
|
215
215
|
|
|
216
216
|
### Loading UI
|
|
217
217
|
|
|
@@ -231,8 +231,8 @@ export default function Loading() {
|
|
|
231
231
|
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
|
|
232
232
|
return (
|
|
233
233
|
<div>
|
|
234
|
-
<h2
|
|
235
|
-
<button onClick={reset}
|
|
234
|
+
<h2>오류 발생</h2>
|
|
235
|
+
<button onClick={reset}>다시 시도</button>
|
|
236
236
|
</div>
|
|
237
237
|
)
|
|
238
238
|
}
|
|
@@ -240,7 +240,7 @@ export default function Error({ error, reset }: { error: Error; reset: () => voi
|
|
|
240
240
|
|
|
241
241
|
---
|
|
242
242
|
|
|
243
|
-
##
|
|
243
|
+
## 네비게이션
|
|
244
244
|
|
|
245
245
|
```typescript
|
|
246
246
|
"use client"
|
|
@@ -250,8 +250,8 @@ import Link from "next/link"
|
|
|
250
250
|
|
|
251
251
|
export function Navigation() {
|
|
252
252
|
const router = useRouter()
|
|
253
|
-
const pathname = usePathname() //
|
|
254
|
-
const searchParams = useSearchParams() //
|
|
253
|
+
const pathname = usePathname() // 현재 경로
|
|
254
|
+
const searchParams = useSearchParams() // 쿼리 파라미터
|
|
255
255
|
|
|
256
256
|
return (
|
|
257
257
|
<>
|
|
@@ -264,6 +264,6 @@ export function Navigation() {
|
|
|
264
264
|
|
|
265
265
|
---
|
|
266
266
|
|
|
267
|
-
##
|
|
267
|
+
## 참조
|
|
268
268
|
|
|
269
|
-
- [Next.js App Router
|
|
269
|
+
- [Next.js App Router 공식 문서](https://nextjs.org/docs/app)
|
|
@@ -1,32 +1,32 @@
|
|
|
1
1
|
# Caching
|
|
2
2
|
|
|
3
|
-
> Next.js
|
|
3
|
+
> Next.js 캐싱 전략
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## 캐시 레벨
|
|
8
8
|
|
|
9
|
-
|
|
|
10
|
-
|
|
11
|
-
| **Request Memoization** |
|
|
12
|
-
| **Data Cache** |
|
|
13
|
-
| **Full Route Cache** |
|
|
14
|
-
| **Router Cache** |
|
|
9
|
+
| 레벨 | 설명 |
|
|
10
|
+
|------|------|
|
|
11
|
+
| **Request Memoization** | 같은 요청 중복 제거 (React) |
|
|
12
|
+
| **Data Cache** | 서버 데이터 캐시 (영구) |
|
|
13
|
+
| **Full Route Cache** | 빌드 시 정적 렌더링 |
|
|
14
|
+
| **Router Cache** | 클라이언트 라우터 캐시 |
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
18
|
## Request Memoization
|
|
19
19
|
|
|
20
20
|
```typescript
|
|
21
|
-
//
|
|
21
|
+
// 같은 요청은 한 번만 실행됨
|
|
22
22
|
async function getUser(id: string) {
|
|
23
23
|
const res = await fetch(`https://api.example.com/users/${id}`)
|
|
24
24
|
return res.json()
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export default async function Page() {
|
|
28
|
-
const user1 = await getUser("1") // fetch
|
|
29
|
-
const user2 = await getUser("1") //
|
|
28
|
+
const user1 = await getUser("1") // fetch 실행
|
|
29
|
+
const user2 = await getUser("1") // 캐시 사용 (중복 제거)
|
|
30
30
|
|
|
31
31
|
return <div>{user1.name}</div>
|
|
32
32
|
}
|
|
@@ -36,52 +36,52 @@ export default async function Page() {
|
|
|
36
36
|
|
|
37
37
|
## Data Cache (fetch)
|
|
38
38
|
|
|
39
|
-
###
|
|
39
|
+
### 기본 (캐시 사용)
|
|
40
40
|
|
|
41
41
|
```typescript
|
|
42
|
-
//
|
|
42
|
+
// 기본적으로 캐시됨
|
|
43
43
|
const res = await fetch("https://api.example.com/posts")
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
###
|
|
46
|
+
### 캐시 비활성화
|
|
47
47
|
|
|
48
48
|
```typescript
|
|
49
|
-
//
|
|
49
|
+
// 매 요청마다 새로 가져옴
|
|
50
50
|
const res = await fetch("https://api.example.com/posts", {
|
|
51
51
|
cache: "no-store",
|
|
52
52
|
})
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
### Revalidate (
|
|
55
|
+
### Revalidate (시간 기반)
|
|
56
56
|
|
|
57
57
|
```typescript
|
|
58
|
-
//
|
|
58
|
+
// 60초마다 재검증
|
|
59
59
|
const res = await fetch("https://api.example.com/posts", {
|
|
60
60
|
next: { revalidate: 60 },
|
|
61
61
|
})
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
### Tag
|
|
64
|
+
### Tag 기반 캐시
|
|
65
65
|
|
|
66
66
|
```typescript
|
|
67
|
-
//
|
|
67
|
+
// 태그 설정
|
|
68
68
|
const res = await fetch("https://api.example.com/posts", {
|
|
69
69
|
next: { tags: ["posts"] },
|
|
70
70
|
})
|
|
71
71
|
|
|
72
|
-
//
|
|
72
|
+
// Server Action에서 태그 무효화
|
|
73
73
|
"use server"
|
|
74
74
|
import { revalidateTag } from "next/cache"
|
|
75
75
|
|
|
76
76
|
export async function createPost(data: PostInput) {
|
|
77
77
|
await prisma.post.create({ data })
|
|
78
|
-
revalidateTag("posts") //
|
|
78
|
+
revalidateTag("posts") // "posts" 태그 캐시 무효화
|
|
79
79
|
}
|
|
80
80
|
```
|
|
81
81
|
|
|
82
82
|
---
|
|
83
83
|
|
|
84
|
-
## unstable_cache (
|
|
84
|
+
## unstable_cache (함수 캐싱)
|
|
85
85
|
|
|
86
86
|
```typescript
|
|
87
87
|
import { unstable_cache } from "next/cache"
|
|
@@ -90,10 +90,10 @@ const getCachedPosts = unstable_cache(
|
|
|
90
90
|
async () => {
|
|
91
91
|
return prisma.post.findMany()
|
|
92
92
|
},
|
|
93
|
-
["posts"], //
|
|
93
|
+
["posts"], // 캐시 키
|
|
94
94
|
{
|
|
95
|
-
revalidate: 60, // 60
|
|
96
|
-
tags: ["posts"], //
|
|
95
|
+
revalidate: 60, // 60초
|
|
96
|
+
tags: ["posts"], // 태그
|
|
97
97
|
}
|
|
98
98
|
)
|
|
99
99
|
|
|
@@ -115,11 +115,11 @@ import { revalidatePath } from "next/cache"
|
|
|
115
115
|
export async function createPost(data: PostInput) {
|
|
116
116
|
const post = await prisma.post.create({ data })
|
|
117
117
|
|
|
118
|
-
//
|
|
118
|
+
// 특정 경로 캐시 무효화
|
|
119
119
|
revalidatePath("/posts")
|
|
120
120
|
revalidatePath(`/posts/${post.id}`)
|
|
121
121
|
|
|
122
|
-
//
|
|
122
|
+
// 레이아웃 포함 모든 캐시 무효화
|
|
123
123
|
revalidatePath("/posts", "layout")
|
|
124
124
|
|
|
125
125
|
return post
|
|
@@ -138,7 +138,7 @@ import { revalidateTag } from "next/cache"
|
|
|
138
138
|
export async function createPost(data: PostInput) {
|
|
139
139
|
const post = await prisma.post.create({ data })
|
|
140
140
|
|
|
141
|
-
//
|
|
141
|
+
// "posts" 태그가 붙은 모든 캐시 무효화
|
|
142
142
|
revalidateTag("posts")
|
|
143
143
|
|
|
144
144
|
return post
|
|
@@ -147,22 +147,22 @@ export async function createPost(data: PostInput) {
|
|
|
147
147
|
|
|
148
148
|
---
|
|
149
149
|
|
|
150
|
-
## Full Route Cache (
|
|
150
|
+
## Full Route Cache (정적 렌더링)
|
|
151
151
|
|
|
152
|
-
###
|
|
152
|
+
### 정적 페이지
|
|
153
153
|
|
|
154
154
|
```typescript
|
|
155
|
-
//
|
|
155
|
+
// 빌드 시 생성 (기본)
|
|
156
156
|
export default async function PostsPage() {
|
|
157
157
|
const posts = await prisma.post.findMany()
|
|
158
158
|
return <PostsList posts={posts} />
|
|
159
159
|
}
|
|
160
160
|
```
|
|
161
161
|
|
|
162
|
-
###
|
|
162
|
+
### 동적 페이지 (캐시 비활성화)
|
|
163
163
|
|
|
164
164
|
```typescript
|
|
165
|
-
//
|
|
165
|
+
// 매 요청마다 렌더링
|
|
166
166
|
export const dynamic = "force-dynamic"
|
|
167
167
|
|
|
168
168
|
export default async function PostsPage() {
|
|
@@ -171,10 +171,10 @@ export default async function PostsPage() {
|
|
|
171
171
|
}
|
|
172
172
|
```
|
|
173
173
|
|
|
174
|
-
### Revalidate (
|
|
174
|
+
### Revalidate (시간 기반)
|
|
175
175
|
|
|
176
176
|
```typescript
|
|
177
|
-
//
|
|
177
|
+
// 60초마다 재생성
|
|
178
178
|
export const revalidate = 60
|
|
179
179
|
|
|
180
180
|
export default async function PostsPage() {
|
|
@@ -190,16 +190,16 @@ export default async function PostsPage() {
|
|
|
190
190
|
```typescript
|
|
191
191
|
// app/posts/page.tsx
|
|
192
192
|
|
|
193
|
-
//
|
|
193
|
+
// 동적 렌더링 강제
|
|
194
194
|
export const dynamic = "force-dynamic" // "auto" | "force-static" | "error"
|
|
195
195
|
|
|
196
|
-
// Revalidate
|
|
196
|
+
// Revalidate 주기 (초)
|
|
197
197
|
export const revalidate = 60 // false | 0 | number
|
|
198
198
|
|
|
199
|
-
//
|
|
199
|
+
// 런타임 설정
|
|
200
200
|
export const runtime = "nodejs" // "edge"
|
|
201
201
|
|
|
202
|
-
//
|
|
202
|
+
// 최대 실행 시간 (초)
|
|
203
203
|
export const maxDuration = 60
|
|
204
204
|
|
|
205
205
|
export default async function PostsPage() {
|
|
@@ -209,7 +209,7 @@ export default async function PostsPage() {
|
|
|
209
209
|
|
|
210
210
|
---
|
|
211
211
|
|
|
212
|
-
## Router Cache (
|
|
212
|
+
## Router Cache (클라이언트)
|
|
213
213
|
|
|
214
214
|
```typescript
|
|
215
215
|
"use client"
|
|
@@ -222,8 +222,8 @@ export function Navigation() {
|
|
|
222
222
|
return (
|
|
223
223
|
<button
|
|
224
224
|
onClick={() => {
|
|
225
|
-
router.push("/posts") //
|
|
226
|
-
router.refresh() //
|
|
225
|
+
router.push("/posts") // 캐시된 페이지 사용
|
|
226
|
+
router.refresh() // 강제 새로고침
|
|
227
227
|
}}
|
|
228
228
|
>
|
|
229
229
|
Go to Posts
|
|
@@ -234,12 +234,12 @@ export function Navigation() {
|
|
|
234
234
|
|
|
235
235
|
---
|
|
236
236
|
|
|
237
|
-
## generateStaticParams (
|
|
237
|
+
## generateStaticParams (동적 라우트)
|
|
238
238
|
|
|
239
239
|
```typescript
|
|
240
240
|
// app/posts/[id]/page.tsx
|
|
241
241
|
|
|
242
|
-
//
|
|
242
|
+
// 빌드 시 생성할 페이지 목록
|
|
243
243
|
export async function generateStaticParams() {
|
|
244
244
|
const posts = await prisma.post.findMany({ select: { id: true } })
|
|
245
245
|
return posts.map(post => ({ id: post.id }))
|
|
@@ -253,40 +253,40 @@ export default async function PostPage({ params }: { params: { id: string } }) {
|
|
|
253
253
|
|
|
254
254
|
---
|
|
255
255
|
|
|
256
|
-
##
|
|
256
|
+
## 캐싱 플로우
|
|
257
257
|
|
|
258
|
-
###
|
|
258
|
+
### 정적 페이지
|
|
259
259
|
|
|
260
260
|
```
|
|
261
|
-
1.
|
|
262
|
-
2.
|
|
263
|
-
3.
|
|
264
|
-
4.
|
|
261
|
+
1. 빌드 시 렌더링
|
|
262
|
+
2. Full Route Cache 저장
|
|
263
|
+
3. 이후 요청은 캐시 사용
|
|
264
|
+
4. revalidate 시간 후 재생성
|
|
265
265
|
```
|
|
266
266
|
|
|
267
|
-
###
|
|
267
|
+
### 동적 페이지
|
|
268
268
|
|
|
269
269
|
```
|
|
270
|
-
1.
|
|
271
|
-
2.
|
|
272
|
-
3.
|
|
273
|
-
4.
|
|
270
|
+
1. 매 요청마다 렌더링
|
|
271
|
+
2. 캐시 없음
|
|
272
|
+
3. Server Actions로 데이터 업데이트
|
|
273
|
+
4. revalidatePath로 특정 경로만 무효화
|
|
274
274
|
```
|
|
275
275
|
|
|
276
276
|
---
|
|
277
277
|
|
|
278
|
-
##
|
|
278
|
+
## 캐시 무효화 전략
|
|
279
279
|
|
|
280
|
-
###
|
|
280
|
+
### 시간 기반
|
|
281
281
|
|
|
282
282
|
```typescript
|
|
283
|
-
//
|
|
283
|
+
// 60초마다 재생성
|
|
284
284
|
export const revalidate = 60
|
|
285
285
|
|
|
286
286
|
const res = await fetch("...", { next: { revalidate: 60 } })
|
|
287
287
|
```
|
|
288
288
|
|
|
289
|
-
###
|
|
289
|
+
### 온디맨드 (Server Actions)
|
|
290
290
|
|
|
291
291
|
```typescript
|
|
292
292
|
"use server"
|
|
@@ -296,34 +296,34 @@ import { revalidatePath, revalidateTag } from "next/cache"
|
|
|
296
296
|
export async function updatePost(id: string, data: PostInput) {
|
|
297
297
|
await prisma.post.update({ where: { id }, data })
|
|
298
298
|
|
|
299
|
-
//
|
|
299
|
+
// 경로 무효화
|
|
300
300
|
revalidatePath(`/posts/${id}`)
|
|
301
301
|
|
|
302
|
-
//
|
|
302
|
+
// 태그 무효화
|
|
303
303
|
revalidateTag("posts")
|
|
304
304
|
}
|
|
305
305
|
```
|
|
306
306
|
|
|
307
307
|
---
|
|
308
308
|
|
|
309
|
-
##
|
|
309
|
+
## 베스트 프랙티스
|
|
310
310
|
|
|
311
311
|
### ✅ DO
|
|
312
312
|
|
|
313
313
|
```typescript
|
|
314
|
-
// 1.
|
|
314
|
+
// 1. 정적 데이터는 기본 캐시 사용
|
|
315
315
|
const posts = await fetch("https://api.example.com/posts")
|
|
316
316
|
|
|
317
|
-
// 2.
|
|
317
|
+
// 2. 동적 데이터는 no-store
|
|
318
318
|
const user = await fetch("https://api.example.com/user", {
|
|
319
319
|
cache: "no-store",
|
|
320
320
|
})
|
|
321
321
|
|
|
322
|
-
// 3.
|
|
322
|
+
// 3. 태그 기반 무효화
|
|
323
323
|
const posts = await fetch("...", { next: { tags: ["posts"] } })
|
|
324
324
|
revalidateTag("posts")
|
|
325
325
|
|
|
326
|
-
// 4.
|
|
326
|
+
// 4. 함수 캐싱
|
|
327
327
|
const getCachedData = unstable_cache(
|
|
328
328
|
async () => prisma.post.findMany(),
|
|
329
329
|
["posts"],
|
|
@@ -334,18 +334,18 @@ const getCachedData = unstable_cache(
|
|
|
334
334
|
### ❌ DON'T
|
|
335
335
|
|
|
336
336
|
```typescript
|
|
337
|
-
// 1.
|
|
338
|
-
const user = await fetch("/api/user") // ❌
|
|
337
|
+
// 1. 민감한 데이터 캐싱
|
|
338
|
+
const user = await fetch("/api/user") // ❌ 개인정보 캐싱 금지
|
|
339
339
|
|
|
340
|
-
// 2.
|
|
341
|
-
revalidatePath("/") // ❌
|
|
340
|
+
// 2. 과도한 revalidatePath
|
|
341
|
+
revalidatePath("/") // ❌ 전체 사이트 무효화
|
|
342
342
|
|
|
343
|
-
// 3.
|
|
344
|
-
export const revalidate = 1 // ❌
|
|
343
|
+
// 3. 짧은 revalidate
|
|
344
|
+
export const revalidate = 1 // ❌ 부하 증가
|
|
345
345
|
```
|
|
346
346
|
|
|
347
347
|
---
|
|
348
348
|
|
|
349
|
-
##
|
|
349
|
+
## 참조
|
|
350
350
|
|
|
351
351
|
- [Next.js Caching](https://nextjs.org/docs/app/building-your-application/caching)
|