@wooojin/forgen 0.4.8 → 0.4.9

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 (122) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/assets/dev-guide/be/README.md +226 -0
  3. package/assets/dev-guide/be/adapters/build-agents-md.sh +63 -0
  4. package/assets/dev-guide/be/principles/common.md +433 -0
  5. package/assets/dev-guide/be/principles/go.md +469 -0
  6. package/assets/dev-guide/be/principles/node.md +388 -0
  7. package/assets/dev-guide/be/skills/go/be-build/SKILL.md +262 -0
  8. package/assets/dev-guide/be/skills/go/be-perf/SKILL.md +308 -0
  9. package/assets/dev-guide/be/skills/go/be-review/SKILL.md +119 -0
  10. package/assets/dev-guide/be/skills/go/be-security/SKILL.md +362 -0
  11. package/assets/dev-guide/be/skills/node/be-build/SKILL.md +239 -0
  12. package/assets/dev-guide/be/skills/node/be-perf/SKILL.md +272 -0
  13. package/assets/dev-guide/be/skills/node/be-review/SKILL.md +118 -0
  14. package/assets/dev-guide/be/skills/node/be-security/SKILL.md +355 -0
  15. package/assets/dev-guide/be/sources/12factor/INDEX.md +53 -0
  16. package/assets/dev-guide/be/sources/api-design/INDEX.md +56 -0
  17. package/assets/dev-guide/be/sources/ddia/INDEX.md +55 -0
  18. package/assets/dev-guide/be/sources/go-runtime/INDEX.md +62 -0
  19. package/assets/dev-guide/be/sources/node-runtime/INDEX.md +60 -0
  20. package/assets/dev-guide/be/sources/otel/INDEX.md +53 -0
  21. package/assets/dev-guide/be/sources/owasp-api/INDEX.md +52 -0
  22. package/assets/dev-guide/be/sources/postgres/INDEX.md +55 -0
  23. package/assets/dev-guide/be/sources/sre-book/INDEX.md +48 -0
  24. package/assets/dev-guide/fe/README.md +197 -0
  25. package/assets/dev-guide/fe/adapters/build-agents-md.sh +63 -0
  26. package/assets/dev-guide/fe/adapters/refresh.sh +68 -0
  27. package/assets/dev-guide/fe/principles/common.md +160 -0
  28. package/assets/dev-guide/fe/principles/react.md +183 -0
  29. package/assets/dev-guide/fe/principles/vue.md +196 -0
  30. package/assets/dev-guide/fe/skills/react/fe-build/SKILL.md +139 -0
  31. package/assets/dev-guide/fe/skills/react/fe-perf/SKILL.md +179 -0
  32. package/assets/dev-guide/fe/skills/react/fe-review/SKILL.md +141 -0
  33. package/assets/dev-guide/fe/skills/vue/fe-build/SKILL.md +148 -0
  34. package/assets/dev-guide/fe/skills/vue/fe-perf/SKILL.md +163 -0
  35. package/assets/dev-guide/fe/skills/vue/fe-review/SKILL.md +136 -0
  36. package/assets/dev-guide/fe/sources/a11y-dx/INDEX.md +41 -0
  37. package/assets/dev-guide/fe/sources/a11y-dx/chrome-devtools-memory.md +150 -0
  38. package/assets/dev-guide/fe/sources/a11y-dx/chrome-devtools-performance.md +99 -0
  39. package/assets/dev-guide/fe/sources/a11y-dx/lighthouse-audits.md +146 -0
  40. package/assets/dev-guide/fe/sources/a11y-dx/react-devtools-profiler.md +128 -0
  41. package/assets/dev-guide/fe/sources/a11y-dx/wcag22-new-criteria.md +174 -0
  42. package/assets/dev-guide/fe/sources/perf/01-core-web-vitals.md +58 -0
  43. package/assets/dev-guide/fe/sources/perf/02-inp.md +83 -0
  44. package/assets/dev-guide/fe/sources/perf/03-lcp-cls.md +130 -0
  45. package/assets/dev-guide/fe/sources/perf/04-speculation-rules.md +148 -0
  46. package/assets/dev-guide/fe/sources/perf/05-view-transitions.md +153 -0
  47. package/assets/dev-guide/fe/sources/perf/06-nextjs-caching.md +188 -0
  48. package/assets/dev-guide/fe/sources/perf/07-server-components.md +181 -0
  49. package/assets/dev-guide/fe/sources/perf/08-ppr.md +133 -0
  50. package/assets/dev-guide/fe/sources/perf/09-nextjs-image.md +200 -0
  51. package/assets/dev-guide/fe/sources/perf/10-optimize-lcp.md +201 -0
  52. package/assets/dev-guide/fe/sources/perf/INDEX.md +88 -0
  53. package/assets/dev-guide/fe/sources/react/INDEX.md +41 -0
  54. package/assets/dev-guide/fe/sources/react/keeping-components-pure.md +135 -0
  55. package/assets/dev-guide/fe/sources/react/no-effect-patterns.md +183 -0
  56. package/assets/dev-guide/fe/sources/react/react-compiler.md +182 -0
  57. package/assets/dev-guide/fe/sources/react/server-components.md +194 -0
  58. package/assets/dev-guide/fe/sources/react/server-functions.md +192 -0
  59. package/assets/dev-guide/fe/sources/react/suspense.md +218 -0
  60. package/assets/dev-guide/fe/sources/react/use-action-state.md +123 -0
  61. package/assets/dev-guide/fe/sources/react/use-form-status.md +158 -0
  62. package/assets/dev-guide/fe/sources/react/use-hook.md +153 -0
  63. package/assets/dev-guide/fe/sources/react/use-optimistic.md +194 -0
  64. package/assets/dev-guide/fe/sources/toss-ff/INDEX.md +58 -0
  65. package/assets/dev-guide/fe/sources/toss-ff/cohesion-code-directory.md +79 -0
  66. package/assets/dev-guide/fe/sources/toss-ff/cohesion-form-fields.md +110 -0
  67. package/assets/dev-guide/fe/sources/toss-ff/cohesion-magic-number.md +47 -0
  68. package/assets/dev-guide/fe/sources/toss-ff/coupling-item-edit-modal.md +124 -0
  69. package/assets/dev-guide/fe/sources/toss-ff/coupling-use-bottom-sheet.md +57 -0
  70. package/assets/dev-guide/fe/sources/toss-ff/coupling-use-page-state.md +71 -0
  71. package/assets/dev-guide/fe/sources/toss-ff/overview-4-principles.md +77 -0
  72. package/assets/dev-guide/fe/sources/toss-ff/predictability-hidden-logic.md +59 -0
  73. package/assets/dev-guide/fe/sources/toss-ff/predictability-http.md +77 -0
  74. package/assets/dev-guide/fe/sources/toss-ff/predictability-use-user.md +110 -0
  75. package/assets/dev-guide/fe/sources/toss-ff/readability-comparison-order.md +52 -0
  76. package/assets/dev-guide/fe/sources/toss-ff/readability-condition-name.md +64 -0
  77. package/assets/dev-guide/fe/sources/toss-ff/readability-login-start-page.md +183 -0
  78. package/assets/dev-guide/fe/sources/toss-ff/readability-magic-number.md +53 -0
  79. package/assets/dev-guide/fe/sources/toss-ff/readability-submit-button.md +73 -0
  80. package/assets/dev-guide/fe/sources/toss-ff/readability-ternary-operator.md +38 -0
  81. package/assets/dev-guide/fe/sources/toss-ff/readability-use-page-state.md +77 -0
  82. package/assets/dev-guide/fe/sources/toss-ff/readability-user-policy.md +98 -0
  83. package/assets/dev-guide/fe/sources/vue/INDEX.md +17 -0
  84. package/assets/dev-guide/fe/sources/vue/composition-api.md +251 -0
  85. package/assets/dev-guide/fe/sources/vue/nuxt-data-fetching.md +232 -0
  86. package/assets/dev-guide/fe/sources/vue/pinia-state-management.md +134 -0
  87. package/assets/dev-guide/fe/sources/vue/reactivity-pitfalls.md +261 -0
  88. package/assets/dev-guide/fe/sources/vue/style-guide-priority-a.md +117 -0
  89. package/assets/dev-guide/fe/sources/vue/style-guide-priority-b.md +231 -0
  90. package/assets/dev-guide/fe/sources/vue/style-guide-priority-c.md +86 -0
  91. package/assets/dev-guide/fe/sources/vue/style-guide-priority-d.md +72 -0
  92. package/dist/cli.js +42 -0
  93. package/dist/core/dashboard-cli.d.ts +12 -0
  94. package/dist/core/dashboard-cli.js +226 -0
  95. package/dist/core/dev-guide-injector.d.ts +26 -0
  96. package/dist/core/dev-guide-injector.js +137 -0
  97. package/dist/core/init.js +53 -0
  98. package/dist/core/lifecycle-classifier.d.ts +23 -0
  99. package/dist/core/lifecycle-classifier.js +104 -0
  100. package/dist/core/observability-backfill.d.ts +31 -0
  101. package/dist/core/observability-backfill.js +178 -0
  102. package/dist/core/observability-store.d.ts +58 -0
  103. package/dist/core/observability-store.js +195 -0
  104. package/dist/core/session-store.js +4 -0
  105. package/dist/core/spawn.d.ts +17 -0
  106. package/dist/core/spawn.js +179 -2
  107. package/dist/core/statusline-cli.js +34 -1
  108. package/dist/engine/compound-extractor.js +39 -0
  109. package/dist/engine/compound-loop.js +6 -0
  110. package/dist/engine/compound-retire.d.ts +20 -0
  111. package/dist/engine/compound-retire.js +85 -0
  112. package/dist/hooks/context-guard.js +25 -1
  113. package/dist/hooks/post-tool-use.js +48 -0
  114. package/dist/hooks/solution-injector.js +93 -0
  115. package/dist/host/install-claude.d.ts +6 -2
  116. package/dist/host/install-claude.js +74 -2
  117. package/dist/host/install-codex.d.ts +4 -0
  118. package/dist/host/install-codex.js +71 -0
  119. package/dist/host/install-orchestrator.js +1 -0
  120. package/package.json +6 -6
  121. package/plugin.json +1 -1
  122. package/scripts/postinstall.js +134 -0
@@ -0,0 +1,188 @@
1
+ ---
2
+ title: Next.js App Router 캐싱 전략 (Next.js 16 기준)
3
+ source: https://nextjs.org/docs/app/building-your-application/caching
4
+ fetched: 2026-05-18
5
+ category: caching
6
+ ---
7
+
8
+ ## 개요
9
+
10
+ Next.js 16의 캐싱 모델은 `cacheComponents` 플래그 도입으로 재편되었다.
11
+ 데이터는 기본 **비캐싱(dynamic)**, 필요한 곳에만 `use cache` 지시어로 캐싱 opt-in.
12
+
13
+ ```typescript
14
+ // next.config.ts
15
+ import type { NextConfig } from 'next'
16
+
17
+ const nextConfig: NextConfig = {
18
+ cacheComponents: true, // use cache + PPR 통합 활성화
19
+ }
20
+
21
+ export default nextConfig
22
+ ```
23
+
24
+ ---
25
+
26
+ ## 캐싱 계층 구조
27
+
28
+ ### 1. fetch() 캐싱
29
+
30
+ ```typescript
31
+ // 캐시 저장
32
+ const data = await fetch('https://api.example.com/data', {
33
+ cache: 'force-cache'
34
+ })
35
+
36
+ // 캐시 없음 (기본값)
37
+ const data = await fetch('https://api.example.com/data')
38
+ // 또는
39
+ const data = await fetch('https://api.example.com/data', {
40
+ cache: 'no-store'
41
+ })
42
+
43
+ // 시간 기반 재검증 (3600초 = 1시간)
44
+ const data = await fetch('https://api.example.com/data', {
45
+ next: { revalidate: 3600 }
46
+ })
47
+
48
+ // 태그 기반 온디맨드 재검증
49
+ const data = await fetch('https://api.example.com/users', {
50
+ next: { tags: ['user'] }
51
+ })
52
+ ```
53
+
54
+ ### 2. unstable_cache (non-fetch 함수)
55
+
56
+ ```typescript
57
+ import { unstable_cache } from 'next/cache'
58
+ import { db } from '@/lib/db'
59
+
60
+ export const getCachedUser = unstable_cache(
61
+ async (id: string) => {
62
+ return db.select().from(users).where(eq(users.id, id)).then(r => r[0])
63
+ },
64
+ ['user'], // 캐시 키 prefix
65
+ {
66
+ tags: ['user'],
67
+ revalidate: 3600,
68
+ }
69
+ )
70
+ ```
71
+
72
+ ### 3. use cache 지시어 (cacheComponents 활성화 시)
73
+
74
+ ```typescript
75
+ // 컴포넌트 레벨 캐싱
76
+ async function CachedDashboard() {
77
+ 'use cache'
78
+ const data = await fetchDashboardData()
79
+ return <Dashboard data={data} />
80
+ }
81
+
82
+ // 함수 레벨 캐싱
83
+ async function getCachedData(id: string) {
84
+ 'use cache'
85
+ return await db.query.findById(id)
86
+ }
87
+ ```
88
+
89
+ ### 4. React cache (요청 단위 메모이제이션)
90
+
91
+ ```typescript
92
+ import { cache } from 'react'
93
+
94
+ // 단일 렌더 패스 내 중복 요청 제거
95
+ export const getPost = cache(async (id: string) => {
96
+ const post = await db.query.posts.findFirst({
97
+ where: eq(posts.id, parseInt(id)),
98
+ })
99
+ return post
100
+ })
101
+ ```
102
+
103
+ ---
104
+
105
+ ## 온디맨드 재검증
106
+
107
+ ### revalidateTag — 태그로 무효화
108
+
109
+ ```typescript
110
+ import { revalidateTag } from 'next/cache'
111
+
112
+ export async function updateUser(id: string) {
113
+ await db.update(users).set({ name: 'new' }).where(eq(users.id, id))
114
+ revalidateTag('user') // 'user' 태그를 가진 모든 캐시 무효화
115
+ }
116
+ ```
117
+
118
+ ### revalidatePath — 경로로 무효화
119
+
120
+ ```typescript
121
+ import { revalidatePath } from 'next/cache'
122
+
123
+ export async function updatePost() {
124
+ await db.update(posts)...
125
+ revalidatePath('/blog') // /blog 경로의 모든 캐시 무효화
126
+ }
127
+ ```
128
+
129
+ ---
130
+
131
+ ## Route Segment Config
132
+
133
+ ```typescript
134
+ // layout.tsx | page.tsx | route.ts
135
+
136
+ // 항상 동적 렌더링
137
+ export const dynamic = 'force-dynamic'
138
+
139
+ // 항상 정적 (빌드 시 생성)
140
+ export const dynamic = 'force-static'
141
+
142
+ // 기본 재검증 주기 설정 (초)
143
+ export const revalidate = 3600
144
+
145
+ // fetch 캐시 정책 일괄 설정
146
+ export const fetchCache = 'force-cache'
147
+ // 'auto' | 'default-cache' | 'only-cache' | 'force-cache'
148
+ // 'default-no-store' | 'only-no-store' | 'force-no-store'
149
+ ```
150
+
151
+ ---
152
+
153
+ ## 데이터 Preload 패턴
154
+
155
+ ```typescript
156
+ // utils/get-item.ts
157
+ import { cache } from 'react'
158
+ import 'server-only'
159
+
160
+ export const getItem = cache(async (id: string) => {
161
+ // DB 또는 API 호출
162
+ })
163
+
164
+ // 블로킹 작업 전에 미리 데이터 로딩 시작
165
+ export const preload = (id: string) => {
166
+ void getItem(id)
167
+ }
168
+ ```
169
+
170
+ ```typescript
171
+ // page.tsx
172
+ import { getItem, preload, checkIsAvailable } from '@/lib/data'
173
+
174
+ export default async function Page({ params }) {
175
+ const { id } = await params
176
+ preload(id) // 즉시 데이터 로딩 시작
177
+ const isAvailable = await checkIsAvailable() // 병렬 진행
178
+ return isAvailable ? <Item id={id} /> : null
179
+ }
180
+ ```
181
+
182
+ ---
183
+
184
+ ## 재검증 빈도 규칙
185
+
186
+ - 라우트의 재검증 빈도 = 해당 라우트 내 레이아웃+페이지 중 **가장 낮은 revalidate 값**
187
+ - 개별 fetch가 라우트 기본값보다 낮은 revalidate를 갖고 있으면 그 값이 전체 라우트에 영향
188
+ - `revalidate` 값은 정적으로 분석 가능해야 함 (`revalidate = 600` O, `revalidate = 60 * 10` X)
@@ -0,0 +1,181 @@
1
+ ---
2
+ title: Next.js Server Components & Client Components
3
+ source: https://nextjs.org/docs/app/building-your-application/rendering/server-components
4
+ fetched: 2026-05-18
5
+ category: caching
6
+ ---
7
+
8
+ ## 기본 원칙
9
+
10
+ App Router에서 레이아웃과 페이지는 기본적으로 **Server Component**.
11
+ 상호작용·브라우저 API가 필요한 경우에만 `'use client'`로 Client Component 지정.
12
+
13
+ ---
14
+
15
+ ## Server vs Client 선택 기준
16
+
17
+ | 필요한 것 | 권장 컴포넌트 |
18
+ |-----------|--------------|
19
+ | DB/API 직접 접근 | Server |
20
+ | API 키·시크릿 보호 | Server |
21
+ | JS 번들 크기 최소화 | Server |
22
+ | FCP 개선, 점진적 스트리밍 | Server |
23
+ | `onClick`, `onChange` 등 이벤트 핸들러 | Client |
24
+ | `useState`, `useEffect` | Client |
25
+ | `localStorage`, `window` 등 브라우저 API | Client |
26
+ | 커스텀 훅 (상태 기반) | Client |
27
+
28
+ ---
29
+
30
+ ## 렌더링 흐름
31
+
32
+ ### 서버 측
33
+ 1. Server Component → RSC Payload (바이너리 포맷) 생성
34
+ 2. Client Component + RSC Payload → HTML prerender
35
+
36
+ > **RSC Payload**: Server Component 렌더 결과 + Client Component 위치 플레이스홀더 + 참조 파일 경로
37
+
38
+ ### 클라이언트 측 (최초 로드)
39
+ 1. HTML → 즉시 non-interactive 프리뷰 표시
40
+ 2. RSC Payload → Client/Server 컴포넌트 트리 조정(reconcile)
41
+ 3. JavaScript → Client Component 하이드레이션 (인터랙티브)
42
+
43
+ ### 이후 네비게이션
44
+ - RSC Payload prefetch + 캐시 → 즉시 네비게이션
45
+ - Client Component는 클라이언트에서만 렌더 (서버 HTML 없음)
46
+
47
+ ---
48
+
49
+ ## 코드 예시
50
+
51
+ ### 기본 패턴: Server + Client 조합
52
+
53
+ ```tsx
54
+ // app/[id]/page.tsx — Server Component
55
+ import LikeButton from '@/app/ui/like-button'
56
+ import { getPost } from '@/lib/data'
57
+
58
+ export default async function Page({
59
+ params,
60
+ }: {
61
+ params: Promise<{ id: string }>
62
+ }) {
63
+ const { id } = await params
64
+ const post = await getPost(id) // 서버에서 직접 DB 접근
65
+
66
+ return (
67
+ <div>
68
+ <h1>{post.title}</h1>
69
+ <LikeButton likes={post.likes} /> {/* Client Component에 props 전달 */}
70
+ </div>
71
+ )
72
+ }
73
+ ```
74
+
75
+ ```tsx
76
+ // app/ui/like-button.tsx — Client Component
77
+ 'use client'
78
+
79
+ import { useState } from 'react'
80
+
81
+ export default function LikeButton({ likes }: { likes: number }) {
82
+ const [count, setCount] = useState(likes)
83
+ return <button onClick={() => setCount(c => c + 1)}>{count} Likes</button>
84
+ }
85
+ ```
86
+
87
+ ### JS 번들 최소화 패턴
88
+
89
+ ```tsx
90
+ // app/layout.tsx — Server Component
91
+ import Search from './search' // Client Component (검색창만)
92
+ import Logo from './logo' // Server Component (정적)
93
+
94
+ export default function Layout({ children }: { children: React.ReactNode }) {
95
+ return (
96
+ <>
97
+ <nav>
98
+ <Logo /> {/* 서버 렌더 */}
99
+ <Search /> {/* 클라이언트 번들 포함 */}
100
+ </nav>
101
+ <main>{children}</main>
102
+ </>
103
+ )
104
+ }
105
+ ```
106
+
107
+ ### Server를 Client에 children으로 전달
108
+
109
+ ```tsx
110
+ // 'use client' Modal에 Server Component인 Cart를 children으로 전달 가능
111
+ // app/ui/modal.tsx
112
+ 'use client'
113
+ export default function Modal({ children }: { children: React.ReactNode }) {
114
+ return <div>{children}</div>
115
+ }
116
+
117
+ // app/page.tsx
118
+ import Modal from './ui/modal'
119
+ import Cart from './ui/cart' // Server Component
120
+
121
+ export default function Page() {
122
+ return (
123
+ <Modal>
124
+ <Cart /> {/* 서버에서 미리 렌더링됨 */}
125
+ </Modal>
126
+ )
127
+ }
128
+ ```
129
+
130
+ ---
131
+
132
+ ## Context Provider 패턴
133
+
134
+ React context는 Server Component에서 직접 사용 불가. Client Component로 감싸기:
135
+
136
+ ```tsx
137
+ // app/theme-provider.tsx
138
+ 'use client'
139
+ import { createContext } from 'react'
140
+
141
+ export const ThemeContext = createContext({})
142
+
143
+ export default function ThemeProvider({ children }: { children: React.ReactNode }) {
144
+ return <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider>
145
+ }
146
+ ```
147
+
148
+ ```tsx
149
+ // app/layout.tsx — Server Component
150
+ import ThemeProvider from './theme-provider'
151
+
152
+ export default function RootLayout({ children }) {
153
+ return (
154
+ <html>
155
+ <body>
156
+ <ThemeProvider>{children}</ThemeProvider>
157
+ {/* Provider를 트리 가능한 한 깊숙이 배치 → 정적 부분 최적화 */}
158
+ </body>
159
+ </html>
160
+ )
161
+ }
162
+ ```
163
+
164
+ ---
165
+
166
+ ## 환경 오염 방지
167
+
168
+ ```javascript
169
+ // lib/data.js — 서버 전용 강제
170
+ import 'server-only' // 클라이언트에서 import 시 빌드 에러
171
+
172
+ export async function getData() {
173
+ const res = await fetch('https://api.example.com', {
174
+ headers: { authorization: process.env.API_KEY }, // 서버에서만 접근
175
+ })
176
+ return res.json()
177
+ }
178
+ ```
179
+
180
+ - `NEXT_PUBLIC_` 접두사 없는 환경변수는 클라이언트 번들에서 빈 문자열로 치환됨
181
+ - 반대로 클라이언트 전용 코드: `client-only` 패키지 사용
@@ -0,0 +1,133 @@
1
+ ---
2
+ title: PPR — Partial Prerendering (부분 프리렌더링)
3
+ source: https://nextjs.org/docs/app/api-reference/next-config-js/ppr
4
+ fetched: 2026-05-18
5
+ category: caching
6
+ ---
7
+
8
+ ## 개요
9
+
10
+ PPR(Partial Prerendering)은 단일 라우트에서 **정적 콘텐츠(HTML shell)**와 **동적 콘텐츠(스트리밍)**를 혼합한다.
11
+ 빌드 시 정적 shell을 즉시 제공하고, 동적 부분은 준비되는 대로 스트림.
12
+
13
+ **Next.js 16**: `cacheComponents: true` 설정 시 PPR이 App Router 기본 동작.
14
+ `experimental.ppr` 플래그와 `experimental_ppr` 라우트 세그먼트 설정은 제거됨.
15
+
16
+ ---
17
+
18
+ ## 활성화 (Next.js 16)
19
+
20
+ ```typescript
21
+ // next.config.ts
22
+ import type { NextConfig } from 'next'
23
+
24
+ const nextConfig: NextConfig = {
25
+ cacheComponents: true, // PPR + use cache + dynamicIO 통합 활성화
26
+ }
27
+
28
+ export default nextConfig
29
+ ```
30
+
31
+ ---
32
+
33
+ ## 동작 원리
34
+
35
+ ```
36
+ 요청 수신
37
+
38
+ 정적 HTML shell → 즉시 응답 (TTFB 최소화)
39
+
40
+ 동적 콘텐츠 → Suspense 경계를 통해 스트리밍
41
+
42
+ 최종 완성된 페이지
43
+ ```
44
+
45
+ ---
46
+
47
+ ## Suspense로 정적/동적 경계 설정
48
+
49
+ ```tsx
50
+ // app/page.tsx
51
+ import { Suspense } from 'react'
52
+ import { StaticContent } from './static-content'
53
+ import { DynamicFeed } from './dynamic-feed'
54
+
55
+ export default function Page() {
56
+ return (
57
+ <main>
58
+ {/* 정적 — 빌드 시 HTML에 포함 */}
59
+ <StaticContent />
60
+
61
+ {/* 동적 — Suspense 경계 안에서 스트리밍 */}
62
+ <Suspense fallback={<FeedSkeleton />}>
63
+ <DynamicFeed />
64
+ </Suspense>
65
+ </main>
66
+ )
67
+ }
68
+ ```
69
+
70
+ ```tsx
71
+ // dynamic-feed.tsx
72
+ async function DynamicFeed() {
73
+ // 이 함수는 request time에 실행됨 (cookies, headers 등 사용 가능)
74
+ const feed = await getUserFeed()
75
+ return <Feed items={feed} />
76
+ }
77
+ ```
78
+
79
+ ---
80
+
81
+ ## use cache 지시어와 함께 사용
82
+
83
+ ```tsx
84
+ // cacheComponents 활성화 시 컴포넌트/함수 레벨 캐싱
85
+ async function CachedSidebar() {
86
+ 'use cache'
87
+ const nav = await getNavigation() // 캐시됨 (정적 shell에 포함)
88
+ return <Sidebar items={nav} />
89
+ }
90
+
91
+ async function DynamicUserPanel() {
92
+ // use cache 없음 → 동적 (스트리밍)
93
+ const user = await getCurrentUser()
94
+ return <UserPanel user={user} />
95
+ }
96
+ ```
97
+
98
+ ---
99
+
100
+ ## Activity 기반 네비게이션 (cacheComponents 활성화 시)
101
+
102
+ React `<Activity>` 컴포넌트로 클라이언트 네비게이션 시 이전 라우트 상태 보존:
103
+ - 라우트 이동 시 이전 라우트를 unmount 대신 `"hidden"` 모드로 전환
104
+ - 뒤로 가기 시 상태 그대로 복원 (form 입력, 스크롤 위치 등)
105
+ - `"hidden"` 상태에서 effects 정리, 복귀 시 재생성
106
+
107
+ ---
108
+
109
+ ## 이전 버전 (Next.js 13-15) experimental PPR
110
+
111
+ ```typescript
112
+ // next.config.ts (Next.js 13-15)
113
+ const nextConfig = {
114
+ experimental: {
115
+ ppr: 'incremental', // 또는 true
116
+ },
117
+ }
118
+
119
+ // 라우트 세그먼트에서 opt-in
120
+ export const experimental_ppr = true
121
+ ```
122
+
123
+ ---
124
+
125
+ ## PPR vs 기존 방식 비교
126
+
127
+ | 방식 | 특징 |
128
+ |------|------|
129
+ | 완전 정적 (SSG) | 빌드 시 모두 생성, 개인화 불가 |
130
+ | 완전 동적 (SSR) | 매 요청마다 렌더, TTFB 높음 |
131
+ | **PPR** | 정적 shell 즉시 + 동적 부분 스트리밍 |
132
+
133
+ PPR은 첫 HTML 응답 속도(≒ TTFB)와 개인화 동적 콘텐츠를 동시에 달성.
@@ -0,0 +1,200 @@
1
+ ---
2
+ title: Next.js Image 컴포넌트 최적화 가이드
3
+ source: https://nextjs.org/docs/app/api-reference/components/image
4
+ fetched: 2026-05-18
5
+ category: images
6
+ ---
7
+
8
+ ## 개요
9
+
10
+ `next/image`는 HTML `<img>`를 확장하여 자동 이미지 최적화를 제공한다:
11
+ - 자동 WebP/AVIF 변환
12
+ - 반응형 srcset 생성
13
+ - Lazy loading 기본 적용
14
+ - CLS 방지를 위한 크기 예약
15
+
16
+ ---
17
+
18
+ ## 기본 사용
19
+
20
+ ```jsx
21
+ import Image from 'next/image'
22
+
23
+ export default function Page() {
24
+ return (
25
+ <Image
26
+ src="/profile.png"
27
+ width={500}
28
+ height={500}
29
+ alt="프로필 사진"
30
+ />
31
+ )
32
+ }
33
+ ```
34
+
35
+ ---
36
+
37
+ ## 핵심 Props 레퍼런스
38
+
39
+ | Prop | 타입 | 기본값 | 설명 |
40
+ |------|------|--------|------|
41
+ | `src` | String | 필수 | 내부 경로, 외부 URL, static import |
42
+ | `alt` | String | 필수 | 접근성 대체 텍스트 |
43
+ | `width` | Integer(px) | - | 인트린식 너비 (aspect ratio 계산용) |
44
+ | `height` | Integer(px) | - | 인트린식 높이 |
45
+ | `fill` | Boolean | false | 부모 요소 크기에 맞게 채우기 |
46
+ | `sizes` | String | - | 반응형 이미지 크기 힌트 |
47
+ | `quality` | Integer(1-100) | 75 | 최적화 품질 |
48
+ | `loading` | String | `'lazy'` | `'lazy'` \| `'eager'` |
49
+ | `preload` | Boolean | false | `<link rel="preload">` 삽입 |
50
+ | `placeholder` | String | `'empty'` | `'empty'` \| `'blur'` \| data URL |
51
+ | `blurDataURL` | String | - | blur placeholder용 base64 이미지 |
52
+ | `unoptimized` | Boolean | false | 최적화 비활성화 |
53
+ | `decoding` | String | `'async'` | 이미지 디코딩 힌트 |
54
+
55
+ > **주의**: `priority` prop은 Next.js 16에서 deprecated → `preload` 사용 권장
56
+
57
+ ---
58
+
59
+ ## LCP 이미지 최적화
60
+
61
+ ```jsx
62
+ // 히어로 이미지 (LCP 요소) — 즉시 로딩
63
+ <Image
64
+ src="/hero.webp"
65
+ width={1200}
66
+ height={600}
67
+ alt="히어로 이미지"
68
+ preload={true} // <link rel="preload"> 삽입
69
+ loading="eager" // lazy loading 비활성화
70
+ quality={85}
71
+ />
72
+ ```
73
+
74
+ **언제 `preload={true}`를 쓰나:**
75
+ - LCP 요소인 경우
76
+ - 폴드 위(above the fold) 이미지
77
+ - `<head>`에서 미리 로드하고 싶은 경우
78
+
79
+ **쓰지 말아야 할 때:**
80
+ - 뷰포트에 따라 LCP 요소가 달라지는 경우
81
+ - `loading` 또는 `fetchPriority` prop과 함께 사용 시
82
+
83
+ ---
84
+
85
+ ## fill + sizes 반응형 패턴
86
+
87
+ ```jsx
88
+ // fill: 부모 요소 크기에 맞게 채움
89
+ // 부모에 position: relative/fixed/absolute 필요
90
+ <div style={{ position: 'relative', width: '100%', height: '400px' }}>
91
+ <Image
92
+ src="/banner.jpg"
93
+ fill
94
+ alt="배너"
95
+ sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
96
+ style={{ objectFit: 'cover' }}
97
+ />
98
+ </div>
99
+ ```
100
+
101
+ **sizes 미지정 시**: 브라우저는 이미지가 100vw라고 가정 → 불필요하게 큰 이미지 다운로드.
102
+
103
+ **sizes 효과**:
104
+ - `sizes` 없음: `1x, 2x` srcset 생성 (고정 크기)
105
+ - `sizes` 있음: `640w, 750w, 828w, ...` 전체 srcset 생성 (반응형)
106
+
107
+ ---
108
+
109
+ ## placeholder blur 패턴
110
+
111
+ ```jsx
112
+ import Image from 'next/image'
113
+ import profilePic from './profile.jpg' // 정적 import → blurDataURL 자동 생성
114
+
115
+ export default function Profile() {
116
+ return (
117
+ <Image
118
+ src={profilePic}
119
+ alt="프로필"
120
+ placeholder="blur" // 로딩 중 블러 표시
121
+ />
122
+ )
123
+ }
124
+ ```
125
+
126
+ 외부 이미지의 경우 `blurDataURL` 직접 제공:
127
+ ```jsx
128
+ <Image
129
+ src="https://example.com/image.jpg"
130
+ width={500}
131
+ height={500}
132
+ alt="외부 이미지"
133
+ placeholder="blur"
134
+ blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgAB..."
135
+ />
136
+ ```
137
+
138
+ ---
139
+
140
+ ## 커스텀 loader
141
+
142
+ ```jsx
143
+ 'use client'
144
+
145
+ import Image from 'next/image'
146
+
147
+ const imageLoader = ({ src, width, quality }) => {
148
+ return `https://cdn.example.com/${src}?w=${width}&q=${quality || 75}`
149
+ }
150
+
151
+ export default function Page() {
152
+ return (
153
+ <Image
154
+ loader={imageLoader}
155
+ src="profile.jpg"
156
+ alt="프로필"
157
+ width={500}
158
+ height={500}
159
+ />
160
+ )
161
+ }
162
+ ```
163
+
164
+ ---
165
+
166
+ ## 외부 이미지 설정 (next.config.ts)
167
+
168
+ ```typescript
169
+ const nextConfig = {
170
+ images: {
171
+ remotePatterns: [
172
+ {
173
+ protocol: 'https',
174
+ hostname: 'cdn.example.com',
175
+ port: '',
176
+ pathname: '/images/**',
177
+ },
178
+ ],
179
+ // 허용 품질 값 제한
180
+ qualities: [75, 85, 100],
181
+ // 포맷 우선순위
182
+ formats: ['image/avif', 'image/webp'],
183
+ },
184
+ }
185
+ ```
186
+
187
+ ---
188
+
189
+ ## CLS 방지 핵심
190
+
191
+ `width`와 `height`는 렌더 크기가 아닌 **aspect ratio 계산**에 사용된다.
192
+ 브라우저가 이미지 로드 전 공간을 예약하여 레이아웃 이동(CLS) 방지.
193
+
194
+ ```jsx
195
+ // fill 없이 사용 시 width + height 필수
196
+ <Image src="/img.jpg" width={800} height={600} alt="..." />
197
+
198
+ // fill 사용 시 width/height 불필요 (부모 크기 따라감)
199
+ <Image src="/img.jpg" fill alt="..." />
200
+ ```