@mandujs/mcp 0.12.2 → 0.13.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/README.md +367 -367
- package/package.json +2 -2
- package/src/activity-monitor.ts +847 -847
- package/src/adapters/index.ts +20 -20
- package/src/adapters/monitor-adapter.ts +100 -100
- package/src/adapters/tool-adapter.ts +88 -88
- package/src/executor/error-handler.ts +250 -250
- package/src/executor/index.ts +22 -22
- package/src/executor/tool-executor.ts +148 -148
- package/src/hooks/config-watcher.ts +174 -174
- package/src/hooks/index.ts +23 -23
- package/src/hooks/mcp-hooks.ts +227 -227
- package/src/index.ts +106 -106
- package/src/logging/index.ts +15 -15
- package/src/logging/mcp-transport.ts +134 -134
- package/src/registry/index.ts +13 -13
- package/src/registry/mcp-tool-registry.ts +298 -298
- package/src/resources/skills/guides.ts +1136 -1136
- package/src/resources/skills/index.ts +12 -12
- package/src/resources/skills/loader.ts +218 -218
- package/src/resources/skills/mandu-composition/SKILL.md +91 -91
- package/src/resources/skills/mandu-composition/metadata.json +13 -13
- package/src/resources/skills/mandu-composition/rules/_sections.md +26 -26
- package/src/resources/skills/mandu-composition/rules/_template.md +77 -77
- package/src/resources/skills/mandu-composition/rules/comp-arch-avoid-boolean-props.md +146 -146
- package/src/resources/skills/mandu-composition/rules/comp-arch-compound-components.md +164 -164
- package/src/resources/skills/mandu-composition/rules/comp-island-event.md +161 -161
- package/src/resources/skills/mandu-composition/rules/comp-island-slot-split.md +167 -167
- package/src/resources/skills/mandu-composition/rules/comp-pattern-children.md +149 -149
- package/src/resources/skills/mandu-composition/rules/comp-state-context-interface.md +148 -148
- package/src/resources/skills/mandu-composition/rules/comp-state-lift-state.md +150 -150
- package/src/resources/skills/mandu-deployment/SKILL.md +92 -92
- package/src/resources/skills/mandu-deployment/_sections.md +41 -41
- package/src/resources/skills/mandu-deployment/_template.md +38 -38
- package/src/resources/skills/mandu-deployment/metadata.json +13 -13
- package/src/resources/skills/mandu-deployment/rules/deploy-build-bun.md +109 -109
- package/src/resources/skills/mandu-deployment/rules/deploy-build-output.md +115 -115
- package/src/resources/skills/mandu-deployment/rules/deploy-cicd-github.md +219 -219
- package/src/resources/skills/mandu-deployment/rules/deploy-docker-bun.md +150 -150
- package/src/resources/skills/mandu-deployment/rules/deploy-docker-compose.md +223 -223
- package/src/resources/skills/mandu-deployment/rules/deploy-platform-fly.md +152 -152
- package/src/resources/skills/mandu-deployment/rules/deploy-platform-render.md +179 -179
- package/src/resources/skills/mandu-deployment/rules/deploy-platform-supabase.md +323 -323
- package/src/resources/skills/mandu-deployment/rules/deploy-platform-vercel.md +140 -140
- package/src/resources/skills/mandu-fs-routes/SKILL.md +82 -82
- package/src/resources/skills/mandu-fs-routes/metadata.json +12 -12
- package/src/resources/skills/mandu-fs-routes/rules/_sections.md +36 -36
- package/src/resources/skills/mandu-fs-routes/rules/_template.md +69 -69
- package/src/resources/skills/mandu-fs-routes/rules/routes-api-methods.md +65 -65
- package/src/resources/skills/mandu-fs-routes/rules/routes-dynamic-param.md +93 -93
- package/src/resources/skills/mandu-fs-routes/rules/routes-naming-page.md +55 -55
- package/src/resources/skills/mandu-guard/SKILL.md +129 -129
- package/src/resources/skills/mandu-guard/metadata.json +12 -12
- package/src/resources/skills/mandu-guard/rules/_sections.md +36 -36
- package/src/resources/skills/mandu-guard/rules/_template.md +82 -82
- package/src/resources/skills/mandu-guard/rules/guard-config-rules.md +100 -100
- package/src/resources/skills/mandu-guard/rules/guard-layer-direction.md +76 -76
- package/src/resources/skills/mandu-guard/rules/guard-preset-mandu.md +81 -81
- package/src/resources/skills/mandu-guard/rules/guard-validate-import.md +80 -80
- package/src/resources/skills/mandu-hydration/SKILL.md +91 -91
- package/src/resources/skills/mandu-hydration/metadata.json +12 -12
- package/src/resources/skills/mandu-hydration/rules/_sections.md +31 -31
- package/src/resources/skills/mandu-hydration/rules/_template.md +72 -72
- package/src/resources/skills/mandu-hydration/rules/hydration-data-event.md +109 -109
- package/src/resources/skills/mandu-hydration/rules/hydration-directive-use-client.md +55 -55
- package/src/resources/skills/mandu-hydration/rules/hydration-island-setup.md +113 -113
- package/src/resources/skills/mandu-hydration/rules/hydration-priority-visible.md +68 -68
- package/src/resources/skills/mandu-performance/SKILL.md +85 -85
- package/src/resources/skills/mandu-performance/metadata.json +14 -14
- package/src/resources/skills/mandu-performance/rules/_sections.md +31 -31
- package/src/resources/skills/mandu-performance/rules/_template.md +64 -64
- package/src/resources/skills/mandu-performance/rules/perf-async-defer-await.md +103 -103
- package/src/resources/skills/mandu-performance/rules/perf-async-parallel.md +95 -95
- package/src/resources/skills/mandu-performance/rules/perf-bun-file.md +124 -124
- package/src/resources/skills/mandu-performance/rules/perf-bun-serve.md +125 -125
- package/src/resources/skills/mandu-performance/rules/perf-bundle-imports.md +80 -80
- package/src/resources/skills/mandu-performance/rules/perf-bundle-island-lazy.md +145 -145
- package/src/resources/skills/mandu-performance/rules/perf-cache-react.md +98 -98
- package/src/resources/skills/mandu-performance/rules/perf-render-transitions.md +154 -154
- package/src/resources/skills/mandu-security/SKILL.md +87 -87
- package/src/resources/skills/mandu-security/metadata.json +13 -13
- package/src/resources/skills/mandu-security/rules/_sections.md +31 -31
- package/src/resources/skills/mandu-security/rules/_template.md +74 -74
- package/src/resources/skills/mandu-security/rules/sec-auth-guard.md +127 -127
- package/src/resources/skills/mandu-security/rules/sec-env-management.md +133 -133
- package/src/resources/skills/mandu-security/rules/sec-input-validate.md +148 -148
- package/src/resources/skills/mandu-security/rules/sec-protect-csrf.md +146 -146
- package/src/resources/skills/mandu-security/rules/sec-protect-headers.md +138 -138
- package/src/resources/skills/mandu-slot/SKILL.md +85 -85
- package/src/resources/skills/mandu-slot/metadata.json +12 -12
- package/src/resources/skills/mandu-slot/rules/_sections.md +36 -36
- package/src/resources/skills/mandu-slot/rules/_template.md +63 -63
- package/src/resources/skills/mandu-slot/rules/slot-basic-structure.md +38 -38
- package/src/resources/skills/mandu-slot/rules/slot-ctx-response.md +56 -56
- package/src/resources/skills/mandu-slot/rules/slot-guard-auth.md +59 -59
- package/src/resources/skills/mandu-slot/rules/slot-http-methods.md +64 -64
- package/src/resources/skills/mandu-styling/SKILL.md +154 -154
- package/src/resources/skills/mandu-styling/_sections.md +43 -43
- package/src/resources/skills/mandu-styling/_template.md +32 -32
- package/src/resources/skills/mandu-styling/metadata.json +15 -15
- package/src/resources/skills/mandu-styling/rules/style-component-compound.md +235 -235
- package/src/resources/skills/mandu-styling/rules/style-component-slots.md +255 -255
- package/src/resources/skills/mandu-styling/rules/style-component-tokens.md +205 -205
- package/src/resources/skills/mandu-styling/rules/style-island-animations.md +272 -272
- package/src/resources/skills/mandu-styling/rules/style-island-scoping.md +167 -167
- package/src/resources/skills/mandu-styling/rules/style-island-variants.md +221 -221
- package/src/resources/skills/mandu-styling/rules/style-perf-critical.md +209 -209
- package/src/resources/skills/mandu-styling/rules/style-perf-purge.md +192 -192
- package/src/resources/skills/mandu-styling/rules/style-setup-modules.md +162 -162
- package/src/resources/skills/mandu-styling/rules/style-setup-panda.md +164 -164
- package/src/resources/skills/mandu-styling/rules/style-setup-tailwind.md +170 -170
- package/src/resources/skills/mandu-styling/rules/style-tailwind-v4-gotchas.md +179 -179
- package/src/resources/skills/mandu-styling/rules/style-theme-darkmode.md +229 -229
- package/src/resources/skills/mandu-testing/SKILL.md +99 -99
- package/src/resources/skills/mandu-testing/metadata.json +13 -13
- package/src/resources/skills/mandu-testing/rules/_sections.md +26 -26
- package/src/resources/skills/mandu-testing/rules/_template.md +65 -65
- package/src/resources/skills/mandu-testing/rules/test-component-island.md +195 -195
- package/src/resources/skills/mandu-testing/rules/test-e2e-playwright.md +196 -196
- package/src/resources/skills/mandu-testing/rules/test-mock-fetch.md +219 -219
- package/src/resources/skills/mandu-testing/rules/test-slot-unit.md +192 -192
- package/src/resources/skills/mandu-ui/SKILL.md +117 -117
- package/src/resources/skills/mandu-ui/_sections.md +23 -23
- package/src/resources/skills/mandu-ui/_template.md +32 -32
- package/src/resources/skills/mandu-ui/metadata.json +13 -13
- package/src/resources/skills/mandu-ui/rules/ui-accessibility-aria.md +232 -232
- package/src/resources/skills/mandu-ui/rules/ui-accessibility-focus.md +238 -238
- package/src/resources/skills/mandu-ui/rules/ui-composition-patterns.md +259 -259
- package/src/resources/skills/mandu-ui/rules/ui-island-integration.md +258 -258
- package/src/resources/skills/mandu-ui/rules/ui-radix-patterns.md +213 -213
- package/src/resources/skills/mandu-ui/rules/ui-shadcn-setup.md +209 -209
- package/src/resources/skills/recipes.ts +932 -932
- package/src/tools/generate.ts +7 -4
- package/src/tools/guard.ts +17 -4
- package/src/tools/hydration.ts +10 -10
- package/src/tools/project.ts +334 -334
- package/src/tools/runtime.ts +497 -497
- package/src/tools/seo.ts +417 -417
- package/src/tools/spec.ts +80 -159
- package/src/utils/project.ts +22 -12
- package/src/utils/withWarnings.ts +83 -83
|
@@ -1,145 +1,145 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Lazy Load Islands with Dynamic Import
|
|
3
|
-
impact: CRITICAL
|
|
4
|
-
impactDescription: 40-60% smaller initial bundle
|
|
5
|
-
tags: performance, bundle, island, lazy, dynamic-import
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Lazy Load Islands with Dynamic Import
|
|
9
|
-
|
|
10
|
-
**Impact: CRITICAL (40-60% smaller initial bundle)**
|
|
11
|
-
|
|
12
|
-
무거운 Island 컴포넌트는 동적 import로 lazy loading하세요. 초기 번들에서 제외되어 페이지 로드가 빨라집니다.
|
|
13
|
-
|
|
14
|
-
**Incorrect (즉시 로드):**
|
|
15
|
-
|
|
16
|
-
```tsx
|
|
17
|
-
// app/dashboard/page.tsx
|
|
18
|
-
import HeavyChart from "./client"; // ❌ 200KB 차트 라이브러리 즉시 로드
|
|
19
|
-
import DataTable from "./table.client"; // ❌ 150KB 테이블 라이브러리 즉시 로드
|
|
20
|
-
|
|
21
|
-
export default function DashboardPage() {
|
|
22
|
-
return (
|
|
23
|
-
<div>
|
|
24
|
-
<h1>Dashboard</h1>
|
|
25
|
-
<HeavyChart data={chartData} />
|
|
26
|
-
<DataTable rows={tableData} />
|
|
27
|
-
</div>
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
**Correct (lazy loading):**
|
|
33
|
-
|
|
34
|
-
```tsx
|
|
35
|
-
// app/dashboard/page.tsx
|
|
36
|
-
import { lazy, Suspense } from "react";
|
|
37
|
-
|
|
38
|
-
// ✅ 동적 import로 코드 스플리팅
|
|
39
|
-
const HeavyChart = lazy(() => import("./client"));
|
|
40
|
-
const DataTable = lazy(() => import("./table.client"));
|
|
41
|
-
|
|
42
|
-
export default function DashboardPage() {
|
|
43
|
-
return (
|
|
44
|
-
<div>
|
|
45
|
-
<h1>Dashboard</h1>
|
|
46
|
-
|
|
47
|
-
<Suspense fallback={<ChartSkeleton />}>
|
|
48
|
-
<HeavyChart data={chartData} />
|
|
49
|
-
</Suspense>
|
|
50
|
-
|
|
51
|
-
<Suspense fallback={<TableSkeleton />}>
|
|
52
|
-
<DataTable rows={tableData} />
|
|
53
|
-
</Suspense>
|
|
54
|
-
</div>
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
## 조건부 로딩
|
|
60
|
-
|
|
61
|
-
사용자 액션 시에만 로드:
|
|
62
|
-
|
|
63
|
-
```tsx
|
|
64
|
-
"use client";
|
|
65
|
-
|
|
66
|
-
import { lazy, Suspense, useState } from "react";
|
|
67
|
-
|
|
68
|
-
const HeavyEditor = lazy(() => import("./editor.client"));
|
|
69
|
-
|
|
70
|
-
export default function EditorToggle() {
|
|
71
|
-
const [showEditor, setShowEditor] = useState(false);
|
|
72
|
-
|
|
73
|
-
return (
|
|
74
|
-
<div>
|
|
75
|
-
<button onClick={() => setShowEditor(true)}>
|
|
76
|
-
Edit
|
|
77
|
-
</button>
|
|
78
|
-
|
|
79
|
-
{showEditor && (
|
|
80
|
-
<Suspense fallback={<p>Loading editor...</p>}>
|
|
81
|
-
<HeavyEditor />
|
|
82
|
-
</Suspense>
|
|
83
|
-
)}
|
|
84
|
-
</div>
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
## Preload on Hover
|
|
90
|
-
|
|
91
|
-
호버 시 미리 로드하여 체감 속도 향상:
|
|
92
|
-
|
|
93
|
-
```tsx
|
|
94
|
-
"use client";
|
|
95
|
-
|
|
96
|
-
import { lazy, Suspense, useState } from "react";
|
|
97
|
-
|
|
98
|
-
// 프리로드 함수 분리
|
|
99
|
-
const editorImport = () => import("./editor.client");
|
|
100
|
-
const HeavyEditor = lazy(editorImport);
|
|
101
|
-
|
|
102
|
-
export default function EditorToggle() {
|
|
103
|
-
const [showEditor, setShowEditor] = useState(false);
|
|
104
|
-
|
|
105
|
-
const handleMouseEnter = () => {
|
|
106
|
-
// ✅ 호버 시 프리로드 시작
|
|
107
|
-
editorImport();
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
return (
|
|
111
|
-
<div>
|
|
112
|
-
<button
|
|
113
|
-
onMouseEnter={handleMouseEnter}
|
|
114
|
-
onClick={() => setShowEditor(true)}
|
|
115
|
-
>
|
|
116
|
-
Edit
|
|
117
|
-
</button>
|
|
118
|
-
|
|
119
|
-
{showEditor && (
|
|
120
|
-
<Suspense fallback={<p>Loading...</p>}>
|
|
121
|
-
<HeavyEditor />
|
|
122
|
-
</Suspense>
|
|
123
|
-
)}
|
|
124
|
-
</div>
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
## Island Priority와 함께 사용
|
|
130
|
-
|
|
131
|
-
```tsx
|
|
132
|
-
// 뷰포트 진입 시 로드 (기본값)
|
|
133
|
-
<Island priority="visible">
|
|
134
|
-
<Suspense fallback={<Skeleton />}>
|
|
135
|
-
<LazyComponent />
|
|
136
|
-
</Suspense>
|
|
137
|
-
</Island>
|
|
138
|
-
|
|
139
|
-
// 브라우저 유휴 시 로드
|
|
140
|
-
<Island priority="idle">
|
|
141
|
-
<Suspense fallback={<Skeleton />}>
|
|
142
|
-
<LazyAnalytics />
|
|
143
|
-
</Suspense>
|
|
144
|
-
</Island>
|
|
145
|
-
```
|
|
1
|
+
---
|
|
2
|
+
title: Lazy Load Islands with Dynamic Import
|
|
3
|
+
impact: CRITICAL
|
|
4
|
+
impactDescription: 40-60% smaller initial bundle
|
|
5
|
+
tags: performance, bundle, island, lazy, dynamic-import
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Lazy Load Islands with Dynamic Import
|
|
9
|
+
|
|
10
|
+
**Impact: CRITICAL (40-60% smaller initial bundle)**
|
|
11
|
+
|
|
12
|
+
무거운 Island 컴포넌트는 동적 import로 lazy loading하세요. 초기 번들에서 제외되어 페이지 로드가 빨라집니다.
|
|
13
|
+
|
|
14
|
+
**Incorrect (즉시 로드):**
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
// app/dashboard/page.tsx
|
|
18
|
+
import HeavyChart from "./client"; // ❌ 200KB 차트 라이브러리 즉시 로드
|
|
19
|
+
import DataTable from "./table.client"; // ❌ 150KB 테이블 라이브러리 즉시 로드
|
|
20
|
+
|
|
21
|
+
export default function DashboardPage() {
|
|
22
|
+
return (
|
|
23
|
+
<div>
|
|
24
|
+
<h1>Dashboard</h1>
|
|
25
|
+
<HeavyChart data={chartData} />
|
|
26
|
+
<DataTable rows={tableData} />
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Correct (lazy loading):**
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
// app/dashboard/page.tsx
|
|
36
|
+
import { lazy, Suspense } from "react";
|
|
37
|
+
|
|
38
|
+
// ✅ 동적 import로 코드 스플리팅
|
|
39
|
+
const HeavyChart = lazy(() => import("./client"));
|
|
40
|
+
const DataTable = lazy(() => import("./table.client"));
|
|
41
|
+
|
|
42
|
+
export default function DashboardPage() {
|
|
43
|
+
return (
|
|
44
|
+
<div>
|
|
45
|
+
<h1>Dashboard</h1>
|
|
46
|
+
|
|
47
|
+
<Suspense fallback={<ChartSkeleton />}>
|
|
48
|
+
<HeavyChart data={chartData} />
|
|
49
|
+
</Suspense>
|
|
50
|
+
|
|
51
|
+
<Suspense fallback={<TableSkeleton />}>
|
|
52
|
+
<DataTable rows={tableData} />
|
|
53
|
+
</Suspense>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## 조건부 로딩
|
|
60
|
+
|
|
61
|
+
사용자 액션 시에만 로드:
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
"use client";
|
|
65
|
+
|
|
66
|
+
import { lazy, Suspense, useState } from "react";
|
|
67
|
+
|
|
68
|
+
const HeavyEditor = lazy(() => import("./editor.client"));
|
|
69
|
+
|
|
70
|
+
export default function EditorToggle() {
|
|
71
|
+
const [showEditor, setShowEditor] = useState(false);
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<div>
|
|
75
|
+
<button onClick={() => setShowEditor(true)}>
|
|
76
|
+
Edit
|
|
77
|
+
</button>
|
|
78
|
+
|
|
79
|
+
{showEditor && (
|
|
80
|
+
<Suspense fallback={<p>Loading editor...</p>}>
|
|
81
|
+
<HeavyEditor />
|
|
82
|
+
</Suspense>
|
|
83
|
+
)}
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Preload on Hover
|
|
90
|
+
|
|
91
|
+
호버 시 미리 로드하여 체감 속도 향상:
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
"use client";
|
|
95
|
+
|
|
96
|
+
import { lazy, Suspense, useState } from "react";
|
|
97
|
+
|
|
98
|
+
// 프리로드 함수 분리
|
|
99
|
+
const editorImport = () => import("./editor.client");
|
|
100
|
+
const HeavyEditor = lazy(editorImport);
|
|
101
|
+
|
|
102
|
+
export default function EditorToggle() {
|
|
103
|
+
const [showEditor, setShowEditor] = useState(false);
|
|
104
|
+
|
|
105
|
+
const handleMouseEnter = () => {
|
|
106
|
+
// ✅ 호버 시 프리로드 시작
|
|
107
|
+
editorImport();
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<div>
|
|
112
|
+
<button
|
|
113
|
+
onMouseEnter={handleMouseEnter}
|
|
114
|
+
onClick={() => setShowEditor(true)}
|
|
115
|
+
>
|
|
116
|
+
Edit
|
|
117
|
+
</button>
|
|
118
|
+
|
|
119
|
+
{showEditor && (
|
|
120
|
+
<Suspense fallback={<p>Loading...</p>}>
|
|
121
|
+
<HeavyEditor />
|
|
122
|
+
</Suspense>
|
|
123
|
+
)}
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Island Priority와 함께 사용
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
// 뷰포트 진입 시 로드 (기본값)
|
|
133
|
+
<Island priority="visible">
|
|
134
|
+
<Suspense fallback={<Skeleton />}>
|
|
135
|
+
<LazyComponent />
|
|
136
|
+
</Suspense>
|
|
137
|
+
</Island>
|
|
138
|
+
|
|
139
|
+
// 브라우저 유휴 시 로드
|
|
140
|
+
<Island priority="idle">
|
|
141
|
+
<Suspense fallback={<Skeleton />}>
|
|
142
|
+
<LazyAnalytics />
|
|
143
|
+
</Suspense>
|
|
144
|
+
</Island>
|
|
145
|
+
```
|
|
@@ -1,98 +1,98 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Use React.cache() for Request Deduplication
|
|
3
|
-
impact: HIGH
|
|
4
|
-
impactDescription: Eliminates duplicate queries within request
|
|
5
|
-
tags: performance, cache, react-cache, deduplication, slot
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Use React.cache() for Request Deduplication
|
|
9
|
-
|
|
10
|
-
**Impact: HIGH (Eliminates duplicate queries within request)**
|
|
11
|
-
|
|
12
|
-
`React.cache()`를 사용하여 단일 요청 내에서 중복 쿼리를 제거하세요. 인증 확인과 데이터베이스 쿼리에 특히 효과적입니다.
|
|
13
|
-
|
|
14
|
-
**사용법:**
|
|
15
|
-
|
|
16
|
-
```typescript
|
|
17
|
-
// lib/auth.ts
|
|
18
|
-
import { cache } from "react";
|
|
19
|
-
|
|
20
|
-
export const getCurrentUser = cache(async () => {
|
|
21
|
-
const session = await auth();
|
|
22
|
-
if (!session?.user?.id) return null;
|
|
23
|
-
|
|
24
|
-
return await db.user.findUnique({
|
|
25
|
-
where: { id: session.user.id },
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
단일 요청 내에서 `getCurrentUser()`를 여러 번 호출해도 쿼리는 한 번만 실행됩니다.
|
|
31
|
-
|
|
32
|
-
**Incorrect (항상 캐시 미스):**
|
|
33
|
-
|
|
34
|
-
```typescript
|
|
35
|
-
// ❌ 인라인 객체는 매번 새 참조 생성
|
|
36
|
-
const getUser = cache(async (params: { uid: number }) => {
|
|
37
|
-
return await db.user.findUnique({ where: { id: params.uid } });
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
// 각 호출이 새 객체, 캐시 미스
|
|
41
|
-
getUser({ uid: 1 });
|
|
42
|
-
getUser({ uid: 1 }); // 캐시 미스, 쿼리 다시 실행
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
**Correct (캐시 히트):**
|
|
46
|
-
|
|
47
|
-
```typescript
|
|
48
|
-
// ✅ 프리미티브 인자는 값 동등성 사용
|
|
49
|
-
const getUser = cache(async (uid: number) => {
|
|
50
|
-
return await db.user.findUnique({ where: { id: uid } });
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
getUser(1);
|
|
54
|
-
getUser(1); // ✅ 캐시 히트, 캐시된 결과 반환
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
## Mandu Slot에서의 활용
|
|
58
|
-
|
|
59
|
-
```typescript
|
|
60
|
-
// lib/data.ts
|
|
61
|
-
import { cache } from "react";
|
|
62
|
-
|
|
63
|
-
export const getProductWithCategory = cache(async (productId: string) => {
|
|
64
|
-
const product = await db.product.findUnique({
|
|
65
|
-
where: { id: productId },
|
|
66
|
-
include: { category: true },
|
|
67
|
-
});
|
|
68
|
-
return product;
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
// spec/slots/product.slot.ts
|
|
72
|
-
import { Mandu } from "@mandujs/core";
|
|
73
|
-
import { getProductWithCategory } from "@/lib/data";
|
|
74
|
-
|
|
75
|
-
export default Mandu.filling()
|
|
76
|
-
.get(async (ctx) => {
|
|
77
|
-
const productId = ctx.params.id;
|
|
78
|
-
|
|
79
|
-
// 여러 컴포넌트에서 호출해도 한 번만 실행
|
|
80
|
-
const product = await getProductWithCategory(productId);
|
|
81
|
-
|
|
82
|
-
return ctx.ok({ product });
|
|
83
|
-
});
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
## React.cache() 적합한 사용 사례
|
|
87
|
-
|
|
88
|
-
- 데이터베이스 쿼리 (Prisma, Drizzle 등)
|
|
89
|
-
- 무거운 계산
|
|
90
|
-
- 인증 확인
|
|
91
|
-
- 파일 시스템 작업
|
|
92
|
-
- fetch가 아닌 모든 비동기 작업
|
|
93
|
-
|
|
94
|
-
## 주의사항
|
|
95
|
-
|
|
96
|
-
`React.cache()`는 요청 단위로 캐시됩니다. 요청 간 캐싱이 필요하면 LRU 캐시를 사용하세요.
|
|
97
|
-
|
|
98
|
-
Reference: [React.cache documentation](https://react.dev/reference/react/cache)
|
|
1
|
+
---
|
|
2
|
+
title: Use React.cache() for Request Deduplication
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Eliminates duplicate queries within request
|
|
5
|
+
tags: performance, cache, react-cache, deduplication, slot
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use React.cache() for Request Deduplication
|
|
9
|
+
|
|
10
|
+
**Impact: HIGH (Eliminates duplicate queries within request)**
|
|
11
|
+
|
|
12
|
+
`React.cache()`를 사용하여 단일 요청 내에서 중복 쿼리를 제거하세요. 인증 확인과 데이터베이스 쿼리에 특히 효과적입니다.
|
|
13
|
+
|
|
14
|
+
**사용법:**
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
// lib/auth.ts
|
|
18
|
+
import { cache } from "react";
|
|
19
|
+
|
|
20
|
+
export const getCurrentUser = cache(async () => {
|
|
21
|
+
const session = await auth();
|
|
22
|
+
if (!session?.user?.id) return null;
|
|
23
|
+
|
|
24
|
+
return await db.user.findUnique({
|
|
25
|
+
where: { id: session.user.id },
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
단일 요청 내에서 `getCurrentUser()`를 여러 번 호출해도 쿼리는 한 번만 실행됩니다.
|
|
31
|
+
|
|
32
|
+
**Incorrect (항상 캐시 미스):**
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
// ❌ 인라인 객체는 매번 새 참조 생성
|
|
36
|
+
const getUser = cache(async (params: { uid: number }) => {
|
|
37
|
+
return await db.user.findUnique({ where: { id: params.uid } });
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// 각 호출이 새 객체, 캐시 미스
|
|
41
|
+
getUser({ uid: 1 });
|
|
42
|
+
getUser({ uid: 1 }); // 캐시 미스, 쿼리 다시 실행
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Correct (캐시 히트):**
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
// ✅ 프리미티브 인자는 값 동등성 사용
|
|
49
|
+
const getUser = cache(async (uid: number) => {
|
|
50
|
+
return await db.user.findUnique({ where: { id: uid } });
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
getUser(1);
|
|
54
|
+
getUser(1); // ✅ 캐시 히트, 캐시된 결과 반환
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Mandu Slot에서의 활용
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
// lib/data.ts
|
|
61
|
+
import { cache } from "react";
|
|
62
|
+
|
|
63
|
+
export const getProductWithCategory = cache(async (productId: string) => {
|
|
64
|
+
const product = await db.product.findUnique({
|
|
65
|
+
where: { id: productId },
|
|
66
|
+
include: { category: true },
|
|
67
|
+
});
|
|
68
|
+
return product;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// spec/slots/product.slot.ts
|
|
72
|
+
import { Mandu } from "@mandujs/core";
|
|
73
|
+
import { getProductWithCategory } from "@/lib/data";
|
|
74
|
+
|
|
75
|
+
export default Mandu.filling()
|
|
76
|
+
.get(async (ctx) => {
|
|
77
|
+
const productId = ctx.params.id;
|
|
78
|
+
|
|
79
|
+
// 여러 컴포넌트에서 호출해도 한 번만 실행
|
|
80
|
+
const product = await getProductWithCategory(productId);
|
|
81
|
+
|
|
82
|
+
return ctx.ok({ product });
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## React.cache() 적합한 사용 사례
|
|
87
|
+
|
|
88
|
+
- 데이터베이스 쿼리 (Prisma, Drizzle 등)
|
|
89
|
+
- 무거운 계산
|
|
90
|
+
- 인증 확인
|
|
91
|
+
- 파일 시스템 작업
|
|
92
|
+
- fetch가 아닌 모든 비동기 작업
|
|
93
|
+
|
|
94
|
+
## 주의사항
|
|
95
|
+
|
|
96
|
+
`React.cache()`는 요청 단위로 캐시됩니다. 요청 간 캐싱이 필요하면 LRU 캐시를 사용하세요.
|
|
97
|
+
|
|
98
|
+
Reference: [React.cache documentation](https://react.dev/reference/react/cache)
|