@mandujs/mcp 0.9.19 → 0.9.21

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/README.md +320 -0
  2. package/package.json +1 -1
  3. package/src/activity-monitor.ts +847 -231
  4. package/src/resources/handlers.ts +244 -0
  5. package/src/resources/skills/guides.ts +1136 -0
  6. package/src/resources/skills/index.ts +12 -0
  7. package/src/resources/skills/loader.ts +218 -0
  8. package/src/resources/skills/mandu-composition/SKILL.md +91 -0
  9. package/src/resources/skills/mandu-composition/metadata.json +13 -0
  10. package/src/resources/skills/mandu-composition/rules/_sections.md +26 -0
  11. package/src/resources/skills/mandu-composition/rules/_template.md +77 -0
  12. package/src/resources/skills/mandu-composition/rules/comp-arch-avoid-boolean-props.md +146 -0
  13. package/src/resources/skills/mandu-composition/rules/comp-arch-compound-components.md +164 -0
  14. package/src/resources/skills/mandu-composition/rules/comp-island-event.md +161 -0
  15. package/src/resources/skills/mandu-composition/rules/comp-island-slot-split.md +167 -0
  16. package/src/resources/skills/mandu-composition/rules/comp-pattern-children.md +149 -0
  17. package/src/resources/skills/mandu-composition/rules/comp-state-context-interface.md +148 -0
  18. package/src/resources/skills/mandu-composition/rules/comp-state-lift-state.md +150 -0
  19. package/src/resources/skills/mandu-deployment/SKILL.md +92 -0
  20. package/src/resources/skills/mandu-deployment/_sections.md +41 -0
  21. package/src/resources/skills/mandu-deployment/_template.md +38 -0
  22. package/src/resources/skills/mandu-deployment/metadata.json +13 -0
  23. package/src/resources/skills/mandu-deployment/rules/deploy-build-bun.md +109 -0
  24. package/src/resources/skills/mandu-deployment/rules/deploy-build-output.md +115 -0
  25. package/src/resources/skills/mandu-deployment/rules/deploy-cicd-github.md +219 -0
  26. package/src/resources/skills/mandu-deployment/rules/deploy-docker-bun.md +150 -0
  27. package/src/resources/skills/mandu-deployment/rules/deploy-docker-compose.md +223 -0
  28. package/src/resources/skills/mandu-deployment/rules/deploy-platform-fly.md +152 -0
  29. package/src/resources/skills/mandu-deployment/rules/deploy-platform-render.md +179 -0
  30. package/src/resources/skills/mandu-deployment/rules/deploy-platform-supabase.md +323 -0
  31. package/src/resources/skills/mandu-deployment/rules/deploy-platform-vercel.md +140 -0
  32. package/src/resources/skills/mandu-fs-routes/SKILL.md +82 -0
  33. package/src/resources/skills/mandu-fs-routes/metadata.json +12 -0
  34. package/src/resources/skills/mandu-fs-routes/rules/_sections.md +36 -0
  35. package/src/resources/skills/mandu-fs-routes/rules/_template.md +69 -0
  36. package/src/resources/skills/mandu-fs-routes/rules/routes-api-methods.md +65 -0
  37. package/src/resources/skills/mandu-fs-routes/rules/routes-dynamic-param.md +93 -0
  38. package/src/resources/skills/mandu-fs-routes/rules/routes-naming-page.md +55 -0
  39. package/src/resources/skills/mandu-guard/SKILL.md +129 -0
  40. package/src/resources/skills/mandu-guard/metadata.json +12 -0
  41. package/src/resources/skills/mandu-guard/rules/_sections.md +36 -0
  42. package/src/resources/skills/mandu-guard/rules/_template.md +82 -0
  43. package/src/resources/skills/mandu-guard/rules/guard-config-rules.md +100 -0
  44. package/src/resources/skills/mandu-guard/rules/guard-layer-direction.md +76 -0
  45. package/src/resources/skills/mandu-guard/rules/guard-preset-mandu.md +81 -0
  46. package/src/resources/skills/mandu-guard/rules/guard-validate-import.md +80 -0
  47. package/src/resources/skills/mandu-hydration/SKILL.md +91 -0
  48. package/src/resources/skills/mandu-hydration/metadata.json +12 -0
  49. package/src/resources/skills/mandu-hydration/rules/_sections.md +31 -0
  50. package/src/resources/skills/mandu-hydration/rules/_template.md +72 -0
  51. package/src/resources/skills/mandu-hydration/rules/hydration-data-event.md +109 -0
  52. package/src/resources/skills/mandu-hydration/rules/hydration-directive-use-client.md +55 -0
  53. package/src/resources/skills/mandu-hydration/rules/hydration-island-setup.md +113 -0
  54. package/src/resources/skills/mandu-hydration/rules/hydration-priority-visible.md +68 -0
  55. package/src/resources/skills/mandu-performance/SKILL.md +85 -0
  56. package/src/resources/skills/mandu-performance/metadata.json +14 -0
  57. package/src/resources/skills/mandu-performance/rules/_sections.md +31 -0
  58. package/src/resources/skills/mandu-performance/rules/_template.md +64 -0
  59. package/src/resources/skills/mandu-performance/rules/perf-async-defer-await.md +103 -0
  60. package/src/resources/skills/mandu-performance/rules/perf-async-parallel.md +95 -0
  61. package/src/resources/skills/mandu-performance/rules/perf-bun-file.md +124 -0
  62. package/src/resources/skills/mandu-performance/rules/perf-bun-serve.md +125 -0
  63. package/src/resources/skills/mandu-performance/rules/perf-bundle-imports.md +80 -0
  64. package/src/resources/skills/mandu-performance/rules/perf-bundle-island-lazy.md +145 -0
  65. package/src/resources/skills/mandu-performance/rules/perf-cache-react.md +98 -0
  66. package/src/resources/skills/mandu-performance/rules/perf-render-transitions.md +154 -0
  67. package/src/resources/skills/mandu-security/SKILL.md +87 -0
  68. package/src/resources/skills/mandu-security/metadata.json +13 -0
  69. package/src/resources/skills/mandu-security/rules/_sections.md +31 -0
  70. package/src/resources/skills/mandu-security/rules/_template.md +74 -0
  71. package/src/resources/skills/mandu-security/rules/sec-auth-guard.md +127 -0
  72. package/src/resources/skills/mandu-security/rules/sec-env-management.md +133 -0
  73. package/src/resources/skills/mandu-security/rules/sec-input-validate.md +148 -0
  74. package/src/resources/skills/mandu-security/rules/sec-protect-csrf.md +146 -0
  75. package/src/resources/skills/mandu-security/rules/sec-protect-headers.md +138 -0
  76. package/src/resources/skills/mandu-slot/SKILL.md +85 -0
  77. package/src/resources/skills/mandu-slot/metadata.json +12 -0
  78. package/src/resources/skills/mandu-slot/rules/_sections.md +36 -0
  79. package/src/resources/skills/mandu-slot/rules/_template.md +63 -0
  80. package/src/resources/skills/mandu-slot/rules/slot-basic-structure.md +38 -0
  81. package/src/resources/skills/mandu-slot/rules/slot-ctx-response.md +56 -0
  82. package/src/resources/skills/mandu-slot/rules/slot-guard-auth.md +59 -0
  83. package/src/resources/skills/mandu-slot/rules/slot-http-methods.md +64 -0
  84. package/src/resources/skills/mandu-styling/SKILL.md +118 -0
  85. package/src/resources/skills/mandu-styling/_sections.md +36 -0
  86. package/src/resources/skills/mandu-styling/_template.md +32 -0
  87. package/src/resources/skills/mandu-styling/metadata.json +13 -0
  88. package/src/resources/skills/mandu-styling/rules/style-component-compound.md +235 -0
  89. package/src/resources/skills/mandu-styling/rules/style-component-slots.md +255 -0
  90. package/src/resources/skills/mandu-styling/rules/style-component-tokens.md +205 -0
  91. package/src/resources/skills/mandu-styling/rules/style-island-animations.md +272 -0
  92. package/src/resources/skills/mandu-styling/rules/style-island-scoping.md +167 -0
  93. package/src/resources/skills/mandu-styling/rules/style-island-variants.md +221 -0
  94. package/src/resources/skills/mandu-styling/rules/style-perf-critical.md +209 -0
  95. package/src/resources/skills/mandu-styling/rules/style-perf-purge.md +192 -0
  96. package/src/resources/skills/mandu-styling/rules/style-setup-modules.md +162 -0
  97. package/src/resources/skills/mandu-styling/rules/style-setup-panda.md +164 -0
  98. package/src/resources/skills/mandu-styling/rules/style-setup-tailwind.md +161 -0
  99. package/src/resources/skills/mandu-styling/rules/style-theme-darkmode.md +229 -0
  100. package/src/resources/skills/mandu-testing/SKILL.md +99 -0
  101. package/src/resources/skills/mandu-testing/metadata.json +13 -0
  102. package/src/resources/skills/mandu-testing/rules/_sections.md +26 -0
  103. package/src/resources/skills/mandu-testing/rules/_template.md +65 -0
  104. package/src/resources/skills/mandu-testing/rules/test-component-island.md +195 -0
  105. package/src/resources/skills/mandu-testing/rules/test-e2e-playwright.md +196 -0
  106. package/src/resources/skills/mandu-testing/rules/test-mock-fetch.md +219 -0
  107. package/src/resources/skills/mandu-testing/rules/test-slot-unit.md +192 -0
  108. package/src/resources/skills/mandu-ui/SKILL.md +117 -0
  109. package/src/resources/skills/mandu-ui/_sections.md +23 -0
  110. package/src/resources/skills/mandu-ui/_template.md +32 -0
  111. package/src/resources/skills/mandu-ui/metadata.json +13 -0
  112. package/src/resources/skills/mandu-ui/rules/ui-accessibility-aria.md +232 -0
  113. package/src/resources/skills/mandu-ui/rules/ui-accessibility-focus.md +238 -0
  114. package/src/resources/skills/mandu-ui/rules/ui-composition-patterns.md +259 -0
  115. package/src/resources/skills/mandu-ui/rules/ui-island-integration.md +258 -0
  116. package/src/resources/skills/mandu-ui/rules/ui-radix-patterns.md +213 -0
  117. package/src/resources/skills/mandu-ui/rules/ui-shadcn-setup.md +209 -0
  118. package/src/resources/skills/recipes.ts +932 -0
  119. package/src/server.ts +3 -0
  120. package/src/tools/hydration.ts +8 -8
  121. package/src/tools/index.ts +1 -0
  122. package/src/tools/seo.ts +417 -0
@@ -0,0 +1,95 @@
1
+ ---
2
+ title: Use Promise.all() for Independent Operations
3
+ impact: CRITICAL
4
+ impactDescription: 2-10× improvement
5
+ tags: performance, async, parallel, promises, slot
6
+ ---
7
+
8
+ ## Use Promise.all() for Independent Operations
9
+
10
+ **Impact: CRITICAL (2-10× improvement)**
11
+
12
+ 독립적인 비동기 작업은 `Promise.all()`로 병렬 실행하세요. 순차 실행은 각 await마다 전체 네트워크 지연을 추가합니다.
13
+
14
+ **Incorrect (순차 실행, 3번의 왕복):**
15
+
16
+ ```typescript
17
+ // spec/slots/dashboard.slot.ts
18
+ import { Mandu } from "@mandujs/core";
19
+
20
+ export default Mandu.filling()
21
+ .get(async (ctx) => {
22
+ // ❌ 순차 실행: 300ms (100ms × 3)
23
+ const user = await fetchUser(ctx.get("userId"));
24
+ const posts = await fetchPosts(ctx.get("userId"));
25
+ const notifications = await fetchNotifications(ctx.get("userId"));
26
+
27
+ return ctx.ok({ user, posts, notifications });
28
+ });
29
+ ```
30
+
31
+ **Correct (병렬 실행, 1번의 왕복):**
32
+
33
+ ```typescript
34
+ // spec/slots/dashboard.slot.ts
35
+ import { Mandu } from "@mandujs/core";
36
+
37
+ export default Mandu.filling()
38
+ .get(async (ctx) => {
39
+ const userId = ctx.get("userId");
40
+
41
+ // ✅ 병렬 실행: 100ms (가장 느린 것 기준)
42
+ const [user, posts, notifications] = await Promise.all([
43
+ fetchUser(userId),
44
+ fetchPosts(userId),
45
+ fetchNotifications(userId),
46
+ ]);
47
+
48
+ return ctx.ok({ user, posts, notifications });
49
+ });
50
+ ```
51
+
52
+ ## 부분 의존성이 있는 경우
53
+
54
+ 일부 작업이 다른 작업의 결과에 의존하는 경우:
55
+
56
+ ```typescript
57
+ export default Mandu.filling()
58
+ .get(async (ctx) => {
59
+ // 1단계: 독립적인 작업 병렬 실행
60
+ const [user, config] = await Promise.all([
61
+ fetchUser(ctx.get("userId")),
62
+ fetchConfig(),
63
+ ]);
64
+
65
+ // 2단계: user에 의존하는 작업 병렬 실행
66
+ const [posts, followers] = await Promise.all([
67
+ fetchPosts(user.id),
68
+ fetchFollowers(user.id),
69
+ ]);
70
+
71
+ return ctx.ok({ user, config, posts, followers });
72
+ });
73
+ ```
74
+
75
+ ## Promise.allSettled() 사용
76
+
77
+ 일부 실패를 허용하는 경우:
78
+
79
+ ```typescript
80
+ const results = await Promise.allSettled([
81
+ fetchUser(userId),
82
+ fetchPosts(userId), // 실패해도 OK
83
+ fetchNotifications(userId), // 실패해도 OK
84
+ ]);
85
+
86
+ const [userResult, postsResult, notificationsResult] = results;
87
+
88
+ return ctx.ok({
89
+ user: userResult.status === "fulfilled" ? userResult.value : null,
90
+ posts: postsResult.status === "fulfilled" ? postsResult.value : [],
91
+ notifications: notificationsResult.status === "fulfilled" ? notificationsResult.value : [],
92
+ });
93
+ ```
94
+
95
+ Reference: [MDN Promise.all()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all)
@@ -0,0 +1,124 @@
1
+ ---
2
+ title: Use Bun.file() for Efficient File Operations
3
+ impact: HIGH
4
+ impactDescription: 10× faster than Node.js fs
5
+ tags: performance, bun, file, io
6
+ ---
7
+
8
+ ## Use Bun.file() for Efficient File Operations
9
+
10
+ **Impact: HIGH (10× faster than Node.js fs)**
11
+
12
+ Bun.file()은 lazy 로딩과 스트리밍을 지원하여 Node.js fs보다 훨씬 빠릅니다.
13
+
14
+ **Incorrect (Node.js 방식):**
15
+
16
+ ```typescript
17
+ import { readFile, writeFile } from "fs/promises";
18
+
19
+ // ❌ 전체 파일을 메모리에 로드
20
+ const content = await readFile("./data.json", "utf-8");
21
+ const data = JSON.parse(content);
22
+ ```
23
+
24
+ **Correct (Bun.file 방식):**
25
+
26
+ ```typescript
27
+ // ✅ Lazy 로딩, 필요할 때만 읽음
28
+ const file = Bun.file("./data.json");
29
+
30
+ // 메타데이터만 읽기 (파일 내용 로드 안 함)
31
+ console.log(file.size); // 빠름
32
+ console.log(file.type); // "application/json"
33
+
34
+ // 필요할 때 내용 읽기
35
+ const data = await file.json(); // JSON 파싱 내장
36
+ ```
37
+
38
+ ## Mandu Slot에서의 활용
39
+
40
+ ```typescript
41
+ // spec/slots/files.slot.ts
42
+ import { Mandu } from "@mandujs/core";
43
+
44
+ export default Mandu.filling()
45
+ .get(async (ctx) => {
46
+ const filename = ctx.params.filename;
47
+ const file = Bun.file(`./uploads/${filename}`);
48
+
49
+ // ✅ 파일 존재 확인
50
+ if (!(await file.exists())) {
51
+ return ctx.notFound("File not found");
52
+ }
53
+
54
+ // ✅ 스트리밍 응답
55
+ return new Response(file, {
56
+ headers: {
57
+ "Content-Type": file.type,
58
+ "Content-Length": String(file.size),
59
+ },
60
+ });
61
+ })
62
+
63
+ .post(async (ctx) => {
64
+ const body = await ctx.body<{ content: string }>();
65
+
66
+ // ✅ 효율적인 파일 쓰기
67
+ await Bun.write("./uploads/new-file.txt", body.content);
68
+
69
+ return ctx.created({ message: "File saved" });
70
+ });
71
+ ```
72
+
73
+ ## 대용량 파일 스트리밍
74
+
75
+ ```typescript
76
+ export default Mandu.filling()
77
+ .get(async (ctx) => {
78
+ const file = Bun.file("./large-video.mp4");
79
+
80
+ // ✅ Range 요청 지원 (비디오 스트리밍)
81
+ const range = ctx.headers.get("range");
82
+
83
+ if (range) {
84
+ const [start, end] = parseRange(range, file.size);
85
+ const chunk = file.slice(start, end + 1);
86
+
87
+ return new Response(chunk, {
88
+ status: 206,
89
+ headers: {
90
+ "Content-Range": `bytes ${start}-${end}/${file.size}`,
91
+ "Content-Length": String(end - start + 1),
92
+ "Content-Type": file.type,
93
+ },
94
+ });
95
+ }
96
+
97
+ return new Response(file);
98
+ });
99
+ ```
100
+
101
+ ## 파일 타입별 메서드
102
+
103
+ ```typescript
104
+ const file = Bun.file("./data.json");
105
+
106
+ // 타입별 파싱 메서드
107
+ await file.text(); // string
108
+ await file.json(); // object
109
+ await file.arrayBuffer(); // ArrayBuffer
110
+ await file.stream(); // ReadableStream
111
+ ```
112
+
113
+ ## 여러 파일 동시 읽기
114
+
115
+ ```typescript
116
+ // ✅ 병렬로 여러 파일 읽기
117
+ const [config, data, schema] = await Promise.all([
118
+ Bun.file("./config.json").json(),
119
+ Bun.file("./data.json").json(),
120
+ Bun.file("./schema.json").json(),
121
+ ]);
122
+ ```
123
+
124
+ Reference: [Bun.file() documentation](https://bun.sh/docs/api/file-io)
@@ -0,0 +1,125 @@
1
+ ---
2
+ title: Optimize Bun.serve() Configuration
3
+ impact: HIGH
4
+ impactDescription: 2-5× faster than Node.js http
5
+ tags: performance, bun, serve, runtime
6
+ ---
7
+
8
+ ## Optimize Bun.serve() Configuration
9
+
10
+ **Impact: HIGH (2-5× faster than Node.js http)**
11
+
12
+ Mandu는 Bun.serve()를 기반으로 합니다. 적절한 설정으로 최대 성능을 끌어내세요.
13
+
14
+ **기본 최적화 설정:**
15
+
16
+ ```typescript
17
+ // server.ts
18
+ Bun.serve({
19
+ port: 3000,
20
+
21
+ // ✅ 개발 환경에서만 에러 스택 노출
22
+ development: process.env.NODE_ENV !== "production",
23
+
24
+ // ✅ 정적 파일 직접 서빙 (미들웨어 우회)
25
+ static: {
26
+ "/public/*": "./public/",
27
+ },
28
+
29
+ fetch(req) {
30
+ // Mandu 라우터 처리
31
+ return handleRequest(req);
32
+ },
33
+
34
+ // ✅ 에러 핸들링
35
+ error(error) {
36
+ return new Response(`Error: ${error.message}`, { status: 500 });
37
+ },
38
+ });
39
+ ```
40
+
41
+ ## 정적 파일 최적화
42
+
43
+ ```typescript
44
+ Bun.serve({
45
+ fetch(req) {
46
+ const url = new URL(req.url);
47
+
48
+ // ✅ 정적 파일은 Bun.file()로 직접 서빙
49
+ if (url.pathname.startsWith("/assets/")) {
50
+ const filePath = `./public${url.pathname}`;
51
+ const file = Bun.file(filePath);
52
+
53
+ return new Response(file, {
54
+ headers: {
55
+ // 캐시 헤더 설정
56
+ "Cache-Control": "public, max-age=31536000, immutable",
57
+ },
58
+ });
59
+ }
60
+
61
+ return handleRequest(req);
62
+ },
63
+ });
64
+ ```
65
+
66
+ ## 스트리밍 응답
67
+
68
+ 대용량 데이터는 스트리밍으로 메모리 효율 향상:
69
+
70
+ ```typescript
71
+ export default Mandu.filling()
72
+ .get(async (ctx) => {
73
+ // ✅ 스트리밍 응답
74
+ const stream = new ReadableStream({
75
+ async start(controller) {
76
+ const cursor = db.query("SELECT * FROM large_table");
77
+
78
+ for await (const row of cursor) {
79
+ controller.enqueue(JSON.stringify(row) + "\n");
80
+ }
81
+
82
+ controller.close();
83
+ },
84
+ });
85
+
86
+ return new Response(stream, {
87
+ headers: { "Content-Type": "application/x-ndjson" },
88
+ });
89
+ });
90
+ ```
91
+
92
+ ## TLS 설정 (HTTPS)
93
+
94
+ ```typescript
95
+ Bun.serve({
96
+ port: 443,
97
+
98
+ // ✅ TLS 인증서 설정
99
+ tls: {
100
+ cert: Bun.file("./cert.pem"),
101
+ key: Bun.file("./key.pem"),
102
+ },
103
+
104
+ fetch(req) {
105
+ return handleRequest(req);
106
+ },
107
+ });
108
+ ```
109
+
110
+ ## 동시 연결 처리
111
+
112
+ Bun은 기본적으로 높은 동시성을 지원하지만, 리소스 제한이 있는 환경에서는:
113
+
114
+ ```typescript
115
+ Bun.serve({
116
+ // ✅ 최대 동시 연결 제한 (메모리 보호)
117
+ maxRequestBodySize: 1024 * 1024 * 10, // 10MB
118
+
119
+ fetch(req) {
120
+ return handleRequest(req);
121
+ },
122
+ });
123
+ ```
124
+
125
+ Reference: [Bun.serve() documentation](https://bun.sh/docs/api/http)
@@ -0,0 +1,80 @@
1
+ ---
2
+ title: Import Directly, Avoid Barrel Files
3
+ impact: CRITICAL
4
+ impactDescription: 15-70% faster dev boot, 40% faster cold starts
5
+ tags: performance, bundle, imports, tree-shaking
6
+ ---
7
+
8
+ ## Import Directly, Avoid Barrel Files
9
+
10
+ **Impact: CRITICAL (15-70% faster dev boot, 40% faster cold starts)**
11
+
12
+ 배럴 파일(index.ts에서 re-export)을 통하지 않고 소스 파일에서 직접 import하세요. 대형 라이브러리의 배럴 파일은 수천 개의 모듈을 로드합니다.
13
+
14
+ **Incorrect (전체 라이브러리 로드):**
15
+
16
+ ```typescript
17
+ // app/page.tsx
18
+ import { Check, X, Menu } from "lucide-react";
19
+ // ❌ 1,583개 모듈 로드, 개발 시 ~2.8초 추가
20
+ // 런타임 비용: 콜드 스타트마다 200-800ms
21
+
22
+ import { Button, TextField } from "@mui/material";
23
+ // ❌ 2,225개 모듈 로드, 개발 시 ~4.2초 추가
24
+ ```
25
+
26
+ **Correct (필요한 것만 로드):**
27
+
28
+ ```typescript
29
+ // app/page.tsx
30
+ import Check from "lucide-react/dist/esm/icons/check";
31
+ import X from "lucide-react/dist/esm/icons/x";
32
+ import Menu from "lucide-react/dist/esm/icons/menu";
33
+ // ✅ 3개 모듈만 로드 (~2KB vs ~1MB)
34
+
35
+ import Button from "@mui/material/Button";
36
+ import TextField from "@mui/material/TextField";
37
+ // ✅ 필요한 것만 로드
38
+ ```
39
+
40
+ ## Mandu 프로젝트에서의 적용
41
+
42
+ ```typescript
43
+ // ❌ 잘못된 방식
44
+ import { useAuth, useUser, usePermissions } from "@/features/auth";
45
+
46
+ // ✅ 올바른 방식
47
+ import { useAuth } from "@/features/auth/hooks/useAuth";
48
+ import { useUser } from "@/features/auth/hooks/useUser";
49
+ ```
50
+
51
+ ## 자체 배럴 파일 피하기
52
+
53
+ ```typescript
54
+ // ❌ features/auth/index.ts (배럴 파일)
55
+ export * from "./hooks/useAuth";
56
+ export * from "./hooks/useUser";
57
+ export * from "./components/LoginForm";
58
+ export * from "./utils/validators";
59
+ // 모든 것을 로드하게 만듦
60
+
61
+ // ✅ 직접 import
62
+ import { useAuth } from "@/features/auth/hooks/useAuth";
63
+ ```
64
+
65
+ ## 영향받는 일반적인 라이브러리
66
+
67
+ - `lucide-react`, `react-icons`
68
+ - `@mui/material`, `@mui/icons-material`
69
+ - `@radix-ui/react-*`
70
+ - `lodash`, `date-fns`
71
+ - `@headlessui/react`
72
+
73
+ ## 번들 분석
74
+
75
+ ```bash
76
+ # 번들 크기 분석
77
+ bunx vite-bundle-visualizer
78
+ ```
79
+
80
+ Reference: [How we optimized package imports in Next.js](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js)
@@ -0,0 +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
+ ```
@@ -0,0 +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)