@jgamaraalv/ts-dev-kit 1.0.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.
Files changed (117) hide show
  1. package/.claude-plugin/marketplace.json +24 -0
  2. package/.claude-plugin/plugin.json +24 -0
  3. package/CHANGELOG.md +24 -0
  4. package/LICENSE +21 -0
  5. package/README.md +128 -0
  6. package/agents/accessibility-pro.md +139 -0
  7. package/agents/api-builder.md +110 -0
  8. package/agents/code-reviewer.md +190 -0
  9. package/agents/database-expert.md +138 -0
  10. package/agents/debugger.md +241 -0
  11. package/agents/docker-expert.md +51 -0
  12. package/agents/multi-agent-coordinator.md +378 -0
  13. package/agents/nextjs-expert.md +136 -0
  14. package/agents/performance-engineer.md +138 -0
  15. package/agents/playwright-expert.md +126 -0
  16. package/agents/react-specialist.md +97 -0
  17. package/agents/security-scanner.md +105 -0
  18. package/agents/test-generator.md +221 -0
  19. package/agents/typescript-pro.md +253 -0
  20. package/agents/ux-optimizer.md +93 -0
  21. package/docs/rules/orchestration.md.template +126 -0
  22. package/package.json +28 -0
  23. package/skills/bullmq/SKILL.md +225 -0
  24. package/skills/bullmq/references/flows-and-schedulers.md +186 -0
  25. package/skills/bullmq/references/job-types-and-options.md +163 -0
  26. package/skills/bullmq/references/patterns.md +273 -0
  27. package/skills/bullmq/references/production.md +308 -0
  28. package/skills/composition-patterns/SKILL.md +58 -0
  29. package/skills/composition-patterns/references/architecture-avoid-boolean-props.md +87 -0
  30. package/skills/composition-patterns/references/architecture-compound-components.md +107 -0
  31. package/skills/composition-patterns/references/patterns-children-over-render-props.md +77 -0
  32. package/skills/composition-patterns/references/patterns-explicit-variants.md +87 -0
  33. package/skills/composition-patterns/references/react19-no-forwardref.md +37 -0
  34. package/skills/composition-patterns/references/state-context-interface.md +194 -0
  35. package/skills/composition-patterns/references/state-decouple-implementation.md +96 -0
  36. package/skills/composition-patterns/references/state-lift-state.md +126 -0
  37. package/skills/conventional-commits/SKILL.md +148 -0
  38. package/skills/docker/SKILL.md +55 -0
  39. package/skills/docker/references/compose-configs.md +95 -0
  40. package/skills/docker/references/monorepo-dockerfile.md +111 -0
  41. package/skills/drizzle-pg/SKILL.md +202 -0
  42. package/skills/drizzle-pg/references/advanced.md +299 -0
  43. package/skills/drizzle-pg/references/migrations.md +214 -0
  44. package/skills/drizzle-pg/references/queries.md +321 -0
  45. package/skills/drizzle-pg/references/relations.md +272 -0
  46. package/skills/drizzle-pg/references/schema-pg.md +256 -0
  47. package/skills/drizzle-pg/references/sql-operator.md +215 -0
  48. package/skills/fastify-best-practices/SKILL.md +143 -0
  49. package/skills/fastify-best-practices/references/hooks-and-lifecycle.md +122 -0
  50. package/skills/fastify-best-practices/references/plugins-and-encapsulation.md +137 -0
  51. package/skills/fastify-best-practices/references/request-reply-errors.md +189 -0
  52. package/skills/fastify-best-practices/references/routes-and-handlers.md +134 -0
  53. package/skills/fastify-best-practices/references/server-and-options.md +127 -0
  54. package/skills/fastify-best-practices/references/typescript-and-logging.md +223 -0
  55. package/skills/fastify-best-practices/references/validation-and-serialization.md +190 -0
  56. package/skills/ioredis/SKILL.md +51 -0
  57. package/skills/ioredis/references/advanced-patterns.md +312 -0
  58. package/skills/ioredis/references/cluster-sentinel.md +280 -0
  59. package/skills/ioredis/references/connection-options.md +187 -0
  60. package/skills/ioredis/references/core-api.md +179 -0
  61. package/skills/nextjs-best-practices/SKILL.md +194 -0
  62. package/skills/nextjs-best-practices/references/async-patterns.md +84 -0
  63. package/skills/nextjs-best-practices/references/bundling.md +192 -0
  64. package/skills/nextjs-best-practices/references/data-patterns.md +310 -0
  65. package/skills/nextjs-best-practices/references/debug-tricks.md +127 -0
  66. package/skills/nextjs-best-practices/references/directives.md +74 -0
  67. package/skills/nextjs-best-practices/references/error-handling.md +237 -0
  68. package/skills/nextjs-best-practices/references/file-conventions.md +152 -0
  69. package/skills/nextjs-best-practices/references/font.md +175 -0
  70. package/skills/nextjs-best-practices/references/functions.md +116 -0
  71. package/skills/nextjs-best-practices/references/hydration-error.md +86 -0
  72. package/skills/nextjs-best-practices/references/image.md +184 -0
  73. package/skills/nextjs-best-practices/references/metadata.md +305 -0
  74. package/skills/nextjs-best-practices/references/parallel-routes.md +299 -0
  75. package/skills/nextjs-best-practices/references/route-handlers.md +154 -0
  76. package/skills/nextjs-best-practices/references/rsc-boundaries.md +168 -0
  77. package/skills/nextjs-best-practices/references/runtime-selection.md +40 -0
  78. package/skills/nextjs-best-practices/references/scripts.md +148 -0
  79. package/skills/nextjs-best-practices/references/self-hosting.md +210 -0
  80. package/skills/nextjs-best-practices/references/suspense-boundaries.md +67 -0
  81. package/skills/owasp-security-review/SKILL.md +98 -0
  82. package/skills/owasp-security-review/references/a01-broken-access-control.md +78 -0
  83. package/skills/owasp-security-review/references/a02-security-misconfiguration.md +81 -0
  84. package/skills/owasp-security-review/references/a03-supply-chain-failures.md +65 -0
  85. package/skills/owasp-security-review/references/a04-cryptographic-failures.md +82 -0
  86. package/skills/owasp-security-review/references/a05-injection.md +106 -0
  87. package/skills/owasp-security-review/references/a06-insecure-design.md +76 -0
  88. package/skills/owasp-security-review/references/a07-authentication-failures.md +83 -0
  89. package/skills/owasp-security-review/references/a08-integrity-failures.md +72 -0
  90. package/skills/owasp-security-review/references/a09-logging-alerting-failures.md +76 -0
  91. package/skills/owasp-security-review/references/a10-exceptional-conditions.md +131 -0
  92. package/skills/postgresql/SKILL.md +50 -0
  93. package/skills/postgresql/references/ddl-schema.md +300 -0
  94. package/skills/postgresql/references/indexes.md +257 -0
  95. package/skills/postgresql/references/jsonb.md +261 -0
  96. package/skills/postgresql/references/performance.md +291 -0
  97. package/skills/postgresql/references/psql-cli.md +153 -0
  98. package/skills/postgresql/references/queries.md +287 -0
  99. package/skills/postgresql/references/transactions.md +280 -0
  100. package/skills/react-best-practices/SKILL.md +110 -0
  101. package/skills/react-best-practices/references/advanced-patterns.md +91 -0
  102. package/skills/react-best-practices/references/async-patterns.md +233 -0
  103. package/skills/react-best-practices/references/bundle-optimization.md +201 -0
  104. package/skills/react-best-practices/references/client-patterns.md +178 -0
  105. package/skills/react-best-practices/references/js-performance.md +210 -0
  106. package/skills/react-best-practices/references/rendering-performance.md +209 -0
  107. package/skills/react-best-practices/references/rerender-optimization.md +316 -0
  108. package/skills/react-best-practices/references/server-performance.md +274 -0
  109. package/skills/service-worker/SKILL.md +195 -0
  110. package/skills/service-worker/references/api-reference.md +114 -0
  111. package/skills/service-worker/references/caching-strategies.md +202 -0
  112. package/skills/service-worker/references/push-and-sync.md +261 -0
  113. package/skills/typescript-conventions/SKILL.md +51 -0
  114. package/skills/ui-ux-guidelines/SKILL.md +105 -0
  115. package/skills/ui-ux-guidelines/references/accessibility-and-interaction.md +74 -0
  116. package/skills/ui-ux-guidelines/references/forms-content-checklist.md +126 -0
  117. package/skills/ui-ux-guidelines/references/layout-typography-animation.md +95 -0
@@ -0,0 +1,299 @@
1
+ # Parallel & Intercepting Routes
2
+
3
+ Parallel routes render multiple pages in the same layout. Intercepting routes show a different UI when navigating from within your app vs direct URL access. Together they enable modal patterns.
4
+
5
+ ## Table of Contents
6
+
7
+ - [File Structure](#file-structure)
8
+ - [Step 1: Root Layout with Slot](#step-1-root-layout-with-slot)
9
+ - [Step 2: Default File (Critical!)](#step-2-default-file-critical)
10
+ - [Step 3: Intercepting Route (Modal)](#step-3-intercepting-route-modal)
11
+ - [Step 4: Full Page (Direct Access)](#step-4-full-page-direct-access)
12
+ - [Step 5: Modal Component with Correct Closing](#step-5-modal-component-with-correct-closing)
13
+ - [Route Matcher Reference](#route-matcher-reference)
14
+ - [Handling Hard Navigation](#handling-hard-navigation)
15
+ - [Common Gotchas](#common-gotchas)
16
+ - [Complete Example: Photo Gallery Modal](#complete-example-photo-gallery-modal)
17
+
18
+ ## File Structure
19
+
20
+ ```
21
+ app/
22
+ ├── @modal/ # Parallel route slot
23
+ │ ├── default.tsx # Required! Returns null
24
+ │ ├── (.)photos/ # Intercepts /photos/*
25
+ │ │ └── [id]/
26
+ │ │ └── page.tsx # Modal content
27
+ │ └── [...]catchall/ # Optional: catch unmatched
28
+ │ └── page.tsx
29
+ ├── photos/
30
+ │ └── [id]/
31
+ │ └── page.tsx # Full page (direct access)
32
+ ├── layout.tsx # Renders both children and @modal
33
+ └── page.tsx
34
+ ```
35
+
36
+ ## Step 1: Root Layout with Slot
37
+
38
+ ```tsx
39
+ // app/layout.tsx
40
+ export default function RootLayout({
41
+ children,
42
+ modal,
43
+ }: {
44
+ children: React.ReactNode;
45
+ modal: React.ReactNode;
46
+ }) {
47
+ return (
48
+ <html>
49
+ <body>
50
+ {children}
51
+ {modal}
52
+ </body>
53
+ </html>
54
+ );
55
+ }
56
+ ```
57
+
58
+ ## Step 2: Default File (Critical!)
59
+
60
+ **Every parallel route slot MUST have a `default.tsx`** to prevent 404s on hard navigation.
61
+
62
+ ```tsx
63
+ // app/@modal/default.tsx
64
+ export default function Default() {
65
+ return null;
66
+ }
67
+ ```
68
+
69
+ Without this file, refreshing any page will 404 because Next.js can't determine what to render in the `@modal` slot.
70
+
71
+ ## Step 3: Intercepting Route (Modal)
72
+
73
+ The `(.)` prefix intercepts routes at the same level.
74
+
75
+ ```tsx
76
+ // app/@modal/(.)photos/[id]/page.tsx
77
+ import { Modal } from "@/components/modal";
78
+
79
+ export default async function PhotoModal({ params }: { params: Promise<{ id: string }> }) {
80
+ const { id } = await params;
81
+ const photo = await getPhoto(id);
82
+
83
+ return (
84
+ <Modal>
85
+ <img src={photo.url} alt={photo.title} />
86
+ </Modal>
87
+ );
88
+ }
89
+ ```
90
+
91
+ ## Step 4: Full Page (Direct Access)
92
+
93
+ ```tsx
94
+ // app/photos/[id]/page.tsx
95
+ export default async function PhotoPage({ params }: { params: Promise<{ id: string }> }) {
96
+ const { id } = await params;
97
+ const photo = await getPhoto(id);
98
+
99
+ return (
100
+ <div className="full-page">
101
+ <img src={photo.url} alt={photo.title} />
102
+ <h1>{photo.title}</h1>
103
+ </div>
104
+ );
105
+ }
106
+ ```
107
+
108
+ ## Step 5: Modal Component with Correct Closing
109
+
110
+ **Critical: Use `router.back()` to close modals, NOT `router.push()` or `<Link>`.**
111
+
112
+ ```tsx
113
+ // components/modal.tsx
114
+ "use client";
115
+
116
+ import { useRouter } from "next/navigation";
117
+ import { useCallback, useEffect, useRef } from "react";
118
+
119
+ export function Modal({ children }: { children: React.ReactNode }) {
120
+ const router = useRouter();
121
+ const overlayRef = useRef<HTMLDivElement>(null);
122
+
123
+ // Close on escape key
124
+ useEffect(() => {
125
+ function onKeyDown(e: KeyboardEvent) {
126
+ if (e.key === "Escape") {
127
+ router.back(); // Correct
128
+ }
129
+ }
130
+ document.addEventListener("keydown", onKeyDown);
131
+ return () => document.removeEventListener("keydown", onKeyDown);
132
+ }, [router]);
133
+
134
+ // Close on overlay click
135
+ const handleOverlayClick = useCallback(
136
+ (e: React.MouseEvent) => {
137
+ if (e.target === overlayRef.current) {
138
+ router.back(); // Correct
139
+ }
140
+ },
141
+ [router],
142
+ );
143
+
144
+ return (
145
+ <div
146
+ ref={overlayRef}
147
+ onClick={handleOverlayClick}
148
+ className="fixed inset-0 bg-black/50 flex items-center justify-center z-50"
149
+ >
150
+ <div className="bg-white rounded-lg p-6 max-w-2xl w-full mx-4">
151
+ <button
152
+ onClick={() => router.back()} // Correct!
153
+ className="absolute top-4 right-4"
154
+ >
155
+ Close
156
+ </button>
157
+ {children}
158
+ </div>
159
+ </div>
160
+ );
161
+ }
162
+ ```
163
+
164
+ ### Why NOT `router.push('/')` or `<Link href="/">`?
165
+
166
+ Using `push` or `Link` to "close" a modal:
167
+
168
+ 1. Adds a new history entry (back button shows modal again)
169
+ 2. Doesn't properly clear the intercepted route
170
+ 3. Can cause the modal to flash or persist unexpectedly
171
+
172
+ `router.back()` correctly:
173
+
174
+ 1. Removes the intercepted route from history
175
+ 2. Returns to the previous page
176
+ 3. Properly unmounts the modal
177
+
178
+ ## Route Matcher Reference
179
+
180
+ Matchers match **route segments**, not filesystem paths:
181
+
182
+ | Matcher | Matches | Example |
183
+ | ---------- | ------------- | --------------------------------------------------------------------- |
184
+ | `(.)` | Same level | `@modal/(.)photos` intercepts `/photos` |
185
+ | `(..)` | One level up | `@modal/(..)settings` from `/dashboard/@modal` intercepts `/settings` |
186
+ | `(..)(..)` | Two levels up | Rarely used |
187
+ | `(...)` | From root | `@modal/(...)photos` intercepts `/photos` from anywhere |
188
+
189
+ **Common mistake**: Thinking `(..)` means "parent folder" - it means "parent route segment".
190
+
191
+ ## Handling Hard Navigation
192
+
193
+ When users directly visit `/photos/123` (bookmark, refresh, shared link):
194
+
195
+ - The intercepting route is bypassed
196
+ - The full `photos/[id]/page.tsx` renders
197
+ - Modal doesn't appear (expected behavior)
198
+
199
+ If you want the modal to appear on direct access too, you need additional logic:
200
+
201
+ ```tsx
202
+ // app/photos/[id]/page.tsx
203
+ import { Modal } from "@/components/modal";
204
+
205
+ export default async function PhotoPage({ params }) {
206
+ const { id } = await params;
207
+ const photo = await getPhoto(id);
208
+
209
+ // Option: Render as modal on direct access too
210
+ return (
211
+ <Modal>
212
+ <img src={photo.url} alt={photo.title} />
213
+ </Modal>
214
+ );
215
+ }
216
+ ```
217
+
218
+ ## Common Gotchas
219
+
220
+ ### 1. Missing `default.tsx` → 404 on Refresh
221
+
222
+ Every `@slot` folder needs a `default.tsx` that returns `null` (or appropriate content).
223
+
224
+ ### 2. Modal Persists After Navigation
225
+
226
+ You're using `router.push()` instead of `router.back()`.
227
+
228
+ ### 3. Nested Parallel Routes Need Defaults Too
229
+
230
+ If you have `@modal` inside a route group, each level needs its own `default.tsx`:
231
+
232
+ ```
233
+ app/
234
+ ├── (marketing)/
235
+ │ ├── @modal/
236
+ │ │ └── default.tsx # Needed!
237
+ │ └── layout.tsx
238
+ └── layout.tsx
239
+ ```
240
+
241
+ ### 4. Intercepted Route Shows Wrong Content
242
+
243
+ Check your matcher:
244
+
245
+ - `(.)photos` intercepts `/photos` from the same route level
246
+ - If your `@modal` is in `app/dashboard/@modal`, use `(.)photos` to intercept `/dashboard/photos`, not `/photos`
247
+
248
+ ### 5. TypeScript Errors with `params`
249
+
250
+ In Next.js 16.1.6+, `params` is a Promise:
251
+
252
+ ```tsx
253
+ // Correct
254
+ export default async function Page({ params }: { params: Promise<{ id: string }> }) {
255
+ const { id } = await params;
256
+ }
257
+ ```
258
+
259
+ ## Complete Example: Photo Gallery Modal
260
+
261
+ ```
262
+ app/
263
+ ├── @modal/
264
+ │ ├── default.tsx
265
+ │ └── (.)photos/
266
+ │ └── [id]/
267
+ │ └── page.tsx
268
+ ├── photos/
269
+ │ ├── page.tsx # Gallery grid
270
+ │ └── [id]/
271
+ │ └── page.tsx # Full photo page
272
+ ├── layout.tsx
273
+ └── page.tsx
274
+ ```
275
+
276
+ Links in the gallery:
277
+
278
+ ```tsx
279
+ // app/photos/page.tsx
280
+ import Link from "next/link";
281
+
282
+ export default async function Gallery() {
283
+ const photos = await getPhotos();
284
+
285
+ return (
286
+ <div className="grid grid-cols-3 gap-4">
287
+ {photos.map((photo) => (
288
+ <Link key={photo.id} href={`/photos/${photo.id}`}>
289
+ <img src={photo.thumbnail} alt={photo.title} />
290
+ </Link>
291
+ ))}
292
+ </div>
293
+ );
294
+ }
295
+ ```
296
+
297
+ Clicking a photo → Modal opens (intercepted)
298
+ Direct URL → Full page renders
299
+ Refresh while modal open → Full page renders
@@ -0,0 +1,154 @@
1
+ # Route Handlers
2
+
3
+ Create API endpoints with `route.ts` files.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Basic Usage](#basic-usage)
8
+ - [Supported Methods](#supported-methods)
9
+ - [GET Handler Conflicts with page.tsx](#get-handler-conflicts-with-pagetsx)
10
+ - [Environment Behavior](#environment-behavior)
11
+ - [Dynamic Route Handlers](#dynamic-route-handlers)
12
+ - [Request Helpers](#request-helpers)
13
+ - [Response Helpers](#response-helpers)
14
+ - [When to Use Route Handlers vs Server Actions](#when-to-use-route-handlers-vs-server-actions)
15
+
16
+ ## Basic Usage
17
+
18
+ ```tsx
19
+ // app/api/users/route.ts
20
+ export async function GET() {
21
+ const users = await getUsers();
22
+ return Response.json(users);
23
+ }
24
+
25
+ export async function POST(request: Request) {
26
+ const body = await request.json();
27
+ const user = await createUser(body);
28
+ return Response.json(user, { status: 201 });
29
+ }
30
+ ```
31
+
32
+ ## Supported Methods
33
+
34
+ `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`
35
+
36
+ ## GET Handler Conflicts with page.tsx
37
+
38
+ **A `route.ts` and `page.tsx` cannot coexist in the same folder.**
39
+
40
+ ```
41
+ app/
42
+ ├── api/
43
+ │ └── users/
44
+ │ └── route.ts # /api/users
45
+ └── users/
46
+ ├── page.tsx # /users (page)
47
+ └── route.ts # Warning: Conflicts with page.tsx!
48
+ ```
49
+
50
+ If you need both a page and an API at the same path, use different paths:
51
+
52
+ ```
53
+ app/
54
+ ├── users/
55
+ │ └── page.tsx # /users (page)
56
+ └── api/
57
+ └── users/
58
+ └── route.ts # /api/users (API)
59
+ ```
60
+
61
+ ## Environment Behavior
62
+
63
+ Route handlers run in a **Server Component-like environment**:
64
+
65
+ - Yes: Can use `async/await`
66
+ - Yes: Can access `cookies()`, `headers()`
67
+ - Yes: Can use Node.js APIs
68
+ - No: Cannot use React hooks
69
+ - No: Cannot use React DOM APIs
70
+ - No: Cannot use browser APIs
71
+
72
+ ```tsx
73
+ // Bad: This won't work - no React DOM in route handlers
74
+ import { renderToString } from "react-dom/server";
75
+
76
+ export async function GET() {
77
+ const html = renderToString(<Component />); // Error!
78
+ return new Response(html);
79
+ }
80
+ ```
81
+
82
+ ## Dynamic Route Handlers
83
+
84
+ ```tsx
85
+ // app/api/users/[id]/route.ts
86
+ export async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {
87
+ const { id } = await params;
88
+ const user = await getUser(id);
89
+
90
+ if (!user) {
91
+ return Response.json({ error: "Not found" }, { status: 404 });
92
+ }
93
+
94
+ return Response.json(user);
95
+ }
96
+ ```
97
+
98
+ ## Request Helpers
99
+
100
+ ```tsx
101
+ export async function GET(request: Request) {
102
+ // URL and search params
103
+ const { searchParams } = new URL(request.url);
104
+ const query = searchParams.get("q");
105
+
106
+ // Headers
107
+ const authHeader = request.headers.get("authorization");
108
+
109
+ // Cookies (Next.js helper)
110
+ const cookieStore = await cookies();
111
+ const token = cookieStore.get("token");
112
+
113
+ return Response.json({ query, token });
114
+ }
115
+ ```
116
+
117
+ ## Response Helpers
118
+
119
+ ```tsx
120
+ // JSON response
121
+ return Response.json({ data });
122
+
123
+ // With status
124
+ return Response.json({ error: "Not found" }, { status: 404 });
125
+
126
+ // With headers
127
+ return Response.json(data, {
128
+ headers: {
129
+ "Cache-Control": "max-age=3600",
130
+ },
131
+ });
132
+
133
+ // Redirect
134
+ return Response.redirect(new URL("/login", request.url));
135
+
136
+ // Stream
137
+ return new Response(stream, {
138
+ headers: { "Content-Type": "text/event-stream" },
139
+ });
140
+ ```
141
+
142
+ ## When to Use Route Handlers vs Server Actions
143
+
144
+ | Use Case | Route Handlers | Server Actions |
145
+ | ------------------------ | -------------- | -------------- |
146
+ | Form submissions | No | Yes |
147
+ | Data mutations from UI | No | Yes |
148
+ | Third-party webhooks | Yes | No |
149
+ | External API consumption | Yes | No |
150
+ | Public REST API | Yes | No |
151
+ | File uploads | Both work | Both work |
152
+
153
+ **Prefer Server Actions** for mutations triggered from your UI.
154
+ **Use Route Handlers** for external integrations and public APIs.
@@ -0,0 +1,168 @@
1
+ # RSC Boundaries
2
+
3
+ Detect and prevent invalid patterns when crossing Server/Client component boundaries.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Detection Rules](#detection-rules)
8
+ - [1. Async Client Components Are Invalid](#1-async-client-components-are-invalid)
9
+ - [2. Non-Serializable Props to Client Components](#2-non-serializable-props-to-client-components)
10
+ - [3. Server Actions Are the Exception](#3-server-actions-are-the-exception)
11
+ - [Quick Reference](#quick-reference)
12
+
13
+ ## Detection Rules
14
+
15
+ ### 1. Async Client Components Are Invalid
16
+
17
+ Client components **cannot** be async functions. Only Server Components can be async.
18
+
19
+ **Detect:** File has `'use client'` AND component is `async function` or returns `Promise`
20
+
21
+ ```tsx
22
+ // Bad: async client component
23
+ "use client";
24
+ export default async function UserProfile() {
25
+ const user = await getUser(); // Cannot await in client component
26
+ return <div>{user.name}</div>;
27
+ }
28
+
29
+ // Good: Remove async, fetch data in parent server component
30
+ // page.tsx (server component - no 'use client')
31
+ export default async function Page() {
32
+ const user = await getUser();
33
+ return <UserProfile user={user} />;
34
+ }
35
+
36
+ // UserProfile.tsx (client component)
37
+ ("use client");
38
+ export function UserProfile({ user }: { user: User }) {
39
+ return <div>{user.name}</div>;
40
+ }
41
+ ```
42
+
43
+ ```tsx
44
+ // Bad: async arrow function client component
45
+ "use client";
46
+ const Dashboard = async () => {
47
+ const data = await fetchDashboard();
48
+ return <div>{data}</div>;
49
+ };
50
+
51
+ // Good: Fetch in server component, pass data down
52
+ ```
53
+
54
+ ### 2. Non-Serializable Props to Client Components
55
+
56
+ Props passed from Server → Client must be JSON-serializable.
57
+
58
+ **Detect:** Server component passes these to a client component:
59
+
60
+ - Functions (except Server Actions with `'use server'`)
61
+ - `Date` objects
62
+ - `Map`, `Set`, `WeakMap`, `WeakSet`
63
+ - Class instances
64
+ - `Symbol` (unless globally registered)
65
+ - Circular references
66
+
67
+ ```tsx
68
+ // Bad: Function prop
69
+ // page.tsx (server)
70
+ export default function Page() {
71
+ const handleClick = () => console.log("clicked");
72
+ return <ClientButton onClick={handleClick} />;
73
+ }
74
+
75
+ // Good: Define function inside client component
76
+ // ClientButton.tsx
77
+ ("use client");
78
+ export function ClientButton() {
79
+ const handleClick = () => console.log("clicked");
80
+ return <button onClick={handleClick}>Click</button>;
81
+ }
82
+ ```
83
+
84
+ ```tsx
85
+ // Bad: Date object (silently becomes string, then crashes)
86
+ // page.tsx (server)
87
+ export default async function Page() {
88
+ const post = await getPost();
89
+ return <PostCard createdAt={post.createdAt} />; // Date object
90
+ }
91
+
92
+ // PostCard.tsx (client) - will crash on .getFullYear()
93
+ ("use client");
94
+ export function PostCard({ createdAt }: { createdAt: Date }) {
95
+ return <span>{createdAt.getFullYear()}</span>; // Runtime error!
96
+ }
97
+
98
+ // Good: Serialize to string on server
99
+ // page.tsx (server)
100
+ export default async function Page() {
101
+ const post = await getPost();
102
+ return <PostCard createdAt={post.createdAt.toISOString()} />;
103
+ }
104
+
105
+ // PostCard.tsx (client)
106
+ ("use client");
107
+ export function PostCard({ createdAt }: { createdAt: string }) {
108
+ const date = new Date(createdAt);
109
+ return <span>{date.getFullYear()}</span>;
110
+ }
111
+ ```
112
+
113
+ ```tsx
114
+ // Bad: Class instance
115
+ const user = new UserModel(data)
116
+ <ClientProfile user={user} /> // Methods will be stripped
117
+
118
+ // Good: Pass plain object
119
+ const user = await getUser()
120
+ <ClientProfile user={{ id: user.id, name: user.name }} />
121
+ ```
122
+
123
+ ```tsx
124
+ // Bad: Map/Set
125
+ <ClientComponent items={new Map([['a', 1]])} />
126
+
127
+ // Good: Convert to array/object
128
+ <ClientComponent items={Object.fromEntries(map)} />
129
+ <ClientComponent items={Array.from(set)} />
130
+ ```
131
+
132
+ ### 3. Server Actions Are the Exception
133
+
134
+ Functions marked with `'use server'` CAN be passed to client components.
135
+
136
+ ```tsx
137
+ // Valid: Server Action can be passed
138
+ // actions.ts
139
+ "use server";
140
+ export async function submitForm(formData: FormData) {
141
+ // server-side logic
142
+ }
143
+
144
+ // page.tsx (server)
145
+ import { submitForm } from "./actions";
146
+ export default function Page() {
147
+ return <ClientForm onSubmit={submitForm} />; // OK!
148
+ }
149
+
150
+ // ClientForm.tsx (client)
151
+ ("use client");
152
+ export function ClientForm({ onSubmit }: { onSubmit: (data: FormData) => Promise<void> }) {
153
+ return <form action={onSubmit}>...</form>;
154
+ }
155
+ ```
156
+
157
+ ## Quick Reference
158
+
159
+ | Pattern | Valid? | Fix |
160
+ | --------------------------------- | ------ | ------------------------------------- |
161
+ | `'use client'` + `async function` | No | Fetch in server parent, pass data |
162
+ | Pass `() => {}` to client | No | Define in client or use server action |
163
+ | Pass `new Date()` to client | No | Use `.toISOString()` |
164
+ | Pass `new Map()` to client | No | Convert to object/array |
165
+ | Pass class instance to client | No | Pass plain object |
166
+ | Pass server action to client | Yes | - |
167
+ | Pass `string/number/boolean` | Yes | - |
168
+ | Pass plain object/array | Yes | - |
@@ -0,0 +1,40 @@
1
+ # Runtime Selection
2
+
3
+ ## Use Node.js Runtime by Default
4
+
5
+ Use the default Node.js runtime for new routes and pages. Only use Edge runtime if the project already uses it or there's a specific requirement.
6
+
7
+ ```tsx
8
+ // Good: Default - no runtime config needed (uses Node.js)
9
+ export default function Page() { ... }
10
+
11
+ // Caution: Only if already used in project or specifically required
12
+ export const runtime = 'edge'
13
+ ```
14
+
15
+ ## When to Use Each
16
+
17
+ ### Node.js Runtime (Default)
18
+
19
+ - Full Node.js API support
20
+ - File system access (`fs`)
21
+ - Full `crypto` support
22
+ - Database connections
23
+ - Most npm packages work
24
+
25
+ ### Edge Runtime
26
+
27
+ - Only for specific edge-location latency requirements
28
+ - Limited API (no `fs`, limited `crypto`)
29
+ - Smaller cold start
30
+ - Geographic distribution needs
31
+
32
+ ## Detection
33
+
34
+ **Before adding `runtime = 'edge'`**, check:
35
+
36
+ 1. Does the project already use Edge runtime?
37
+ 2. Is there a specific latency requirement?
38
+ 3. Are all dependencies Edge-compatible?
39
+
40
+ If unsure, use Node.js runtime.