@0xsown/vibe-code-fe 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.
- package/bin/index.js +181 -0
- package/package.json +32 -0
- package/skills/claude-md-improver/SKILL.md +179 -0
- package/skills/claude-md-improver/references/quality-criteria.md +109 -0
- package/skills/claude-md-improver/references/templates.md +253 -0
- package/skills/claude-md-improver/references/update-guidelines.md +150 -0
- package/skills/find-skills/SKILL.md +133 -0
- package/skills/frontend-design/LICENSE.txt +177 -0
- package/skills/frontend-design/SKILL.md +42 -0
- package/skills/next-best-practices/SKILL.md +153 -0
- package/skills/next-best-practices/async-patterns.md +87 -0
- package/skills/next-best-practices/bundling.md +180 -0
- package/skills/next-best-practices/data-patterns.md +297 -0
- package/skills/next-best-practices/debug-tricks.md +105 -0
- package/skills/next-best-practices/directives.md +73 -0
- package/skills/next-best-practices/error-handling.md +227 -0
- package/skills/next-best-practices/file-conventions.md +140 -0
- package/skills/next-best-practices/font.md +245 -0
- package/skills/next-best-practices/functions.md +108 -0
- package/skills/next-best-practices/hydration-error.md +91 -0
- package/skills/next-best-practices/image.md +173 -0
- package/skills/next-best-practices/metadata.md +301 -0
- package/skills/next-best-practices/parallel-routes.md +287 -0
- package/skills/next-best-practices/route-handlers.md +146 -0
- package/skills/next-best-practices/rsc-boundaries.md +159 -0
- package/skills/next-best-practices/runtime-selection.md +39 -0
- package/skills/next-best-practices/scripts.md +141 -0
- package/skills/next-best-practices/self-hosting.md +371 -0
- package/skills/next-best-practices/suspense-boundaries.md +67 -0
- package/skills/next-cache-components/SKILL.md +411 -0
- package/skills/shadcn-ui/README.md +248 -0
- package/skills/shadcn-ui/SKILL.md +326 -0
- package/skills/shadcn-ui/examples/auth-layout.tsx +177 -0
- package/skills/shadcn-ui/examples/data-table.tsx +313 -0
- package/skills/shadcn-ui/examples/form-pattern.tsx +177 -0
- package/skills/shadcn-ui/resources/component-catalog.md +481 -0
- package/skills/shadcn-ui/resources/customization-guide.md +516 -0
- package/skills/shadcn-ui/resources/migration-guide.md +463 -0
- package/skills/shadcn-ui/resources/setup-guide.md +412 -0
- package/skills/shadcn-ui/scripts/verify-setup.sh +134 -0
- package/skills/supabase-postgres-best-practices/AGENTS.md +68 -0
- package/skills/supabase-postgres-best-practices/CLAUDE.md +68 -0
- package/skills/supabase-postgres-best-practices/README.md +116 -0
- package/skills/supabase-postgres-best-practices/SKILL.md +64 -0
- package/skills/supabase-postgres-best-practices/references/advanced-full-text-search.md +55 -0
- package/skills/supabase-postgres-best-practices/references/advanced-jsonb-indexing.md +49 -0
- package/skills/supabase-postgres-best-practices/references/conn-idle-timeout.md +46 -0
- package/skills/supabase-postgres-best-practices/references/conn-limits.md +44 -0
- package/skills/supabase-postgres-best-practices/references/conn-pooling.md +41 -0
- package/skills/supabase-postgres-best-practices/references/conn-prepared-statements.md +46 -0
- package/skills/supabase-postgres-best-practices/references/data-batch-inserts.md +54 -0
- package/skills/supabase-postgres-best-practices/references/data-n-plus-one.md +53 -0
- package/skills/supabase-postgres-best-practices/references/data-pagination.md +50 -0
- package/skills/supabase-postgres-best-practices/references/data-upsert.md +50 -0
- package/skills/supabase-postgres-best-practices/references/lock-advisory.md +56 -0
- package/skills/supabase-postgres-best-practices/references/lock-deadlock-prevention.md +68 -0
- package/skills/supabase-postgres-best-practices/references/lock-short-transactions.md +50 -0
- package/skills/supabase-postgres-best-practices/references/lock-skip-locked.md +54 -0
- package/skills/supabase-postgres-best-practices/references/monitor-explain-analyze.md +45 -0
- package/skills/supabase-postgres-best-practices/references/monitor-pg-stat-statements.md +55 -0
- package/skills/supabase-postgres-best-practices/references/monitor-vacuum-analyze.md +55 -0
- package/skills/supabase-postgres-best-practices/references/query-composite-indexes.md +44 -0
- package/skills/supabase-postgres-best-practices/references/query-covering-indexes.md +40 -0
- package/skills/supabase-postgres-best-practices/references/query-index-types.md +48 -0
- package/skills/supabase-postgres-best-practices/references/query-missing-indexes.md +43 -0
- package/skills/supabase-postgres-best-practices/references/query-partial-indexes.md +45 -0
- package/skills/supabase-postgres-best-practices/references/schema-constraints.md +80 -0
- package/skills/supabase-postgres-best-practices/references/schema-data-types.md +46 -0
- package/skills/supabase-postgres-best-practices/references/schema-foreign-key-indexes.md +59 -0
- package/skills/supabase-postgres-best-practices/references/schema-lowercase-identifiers.md +55 -0
- package/skills/supabase-postgres-best-practices/references/schema-partitioning.md +55 -0
- package/skills/supabase-postgres-best-practices/references/schema-primary-keys.md +61 -0
- package/skills/supabase-postgres-best-practices/references/security-privileges.md +54 -0
- package/skills/supabase-postgres-best-practices/references/security-rls-basics.md +50 -0
- package/skills/supabase-postgres-best-practices/references/security-rls-performance.md +57 -0
- package/skills/tailwind-design-system/SKILL.md +874 -0
- package/skills/vercel-composition-patterns/AGENTS.md +946 -0
- package/skills/vercel-composition-patterns/README.md +60 -0
- package/skills/vercel-composition-patterns/SKILL.md +89 -0
- package/skills/vercel-composition-patterns/rules/architecture-avoid-boolean-props.md +100 -0
- package/skills/vercel-composition-patterns/rules/architecture-compound-components.md +112 -0
- package/skills/vercel-composition-patterns/rules/patterns-children-over-render-props.md +87 -0
- package/skills/vercel-composition-patterns/rules/patterns-explicit-variants.md +100 -0
- package/skills/vercel-composition-patterns/rules/react19-no-forwardref.md +42 -0
- package/skills/vercel-composition-patterns/rules/state-context-interface.md +191 -0
- package/skills/vercel-composition-patterns/rules/state-decouple-implementation.md +113 -0
- package/skills/vercel-composition-patterns/rules/state-lift-state.md +125 -0
- package/skills/vercel-react-best-practices/AGENTS.md +2934 -0
- package/skills/vercel-react-best-practices/README.md +123 -0
- package/skills/vercel-react-best-practices/SKILL.md +136 -0
- package/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/skills/vercel-react-best-practices/rules/advanced-init-once.md +42 -0
- package/skills/vercel-react-best-practices/rules/advanced-use-latest.md +39 -0
- package/skills/vercel-react-best-practices/rules/async-api-routes.md +38 -0
- package/skills/vercel-react-best-practices/rules/async-defer-await.md +80 -0
- package/skills/vercel-react-best-practices/rules/async-dependencies.md +51 -0
- package/skills/vercel-react-best-practices/rules/async-parallel.md +28 -0
- package/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/skills/vercel-react-best-practices/rules/bundle-conditional.md +31 -0
- package/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/skills/vercel-react-best-practices/rules/bundle-preload.md +50 -0
- package/skills/vercel-react-best-practices/rules/client-event-listeners.md +74 -0
- package/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +71 -0
- package/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/skills/vercel-react-best-practices/rules/client-swr-dedup.md +56 -0
- package/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +107 -0
- package/skills/vercel-react-best-practices/rules/js-cache-function-results.md +80 -0
- package/skills/vercel-react-best-practices/rules/js-cache-property-access.md +28 -0
- package/skills/vercel-react-best-practices/rules/js-cache-storage.md +70 -0
- package/skills/vercel-react-best-practices/rules/js-combine-iterations.md +32 -0
- package/skills/vercel-react-best-practices/rules/js-early-exit.md +50 -0
- package/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/skills/vercel-react-best-practices/rules/js-index-maps.md +37 -0
- package/skills/vercel-react-best-practices/rules/js-length-check-first.md +49 -0
- package/skills/vercel-react-best-practices/rules/js-min-max-loop.md +82 -0
- package/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/skills/vercel-react-best-practices/rules/rendering-activity.md +26 -0
- package/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md +30 -0
- package/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md +75 -0
- package/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/skills/vercel-react-best-practices/rules/rerender-dependencies.md +45 -0
- package/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
- package/skills/vercel-react-best-practices/rules/rerender-derived-state.md +29 -0
- package/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md +38 -0
- package/skills/vercel-react-best-practices/rules/rerender-memo.md +44 -0
- package/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
- package/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
- package/skills/vercel-react-best-practices/rules/rerender-transitions.md +40 -0
- package/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
- package/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/skills/vercel-react-best-practices/rules/server-auth-actions.md +96 -0
- package/skills/vercel-react-best-practices/rules/server-cache-lru.md +41 -0
- package/skills/vercel-react-best-practices/rules/server-cache-react.md +76 -0
- package/skills/vercel-react-best-practices/rules/server-dedup-props.md +65 -0
- package/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/skills/vercel-react-best-practices/rules/server-serialization.md +38 -0
- package/skills/vercel-react-native-skills/AGENTS.md +2897 -0
- package/skills/vercel-react-native-skills/README.md +165 -0
- package/skills/vercel-react-native-skills/SKILL.md +121 -0
- package/skills/vercel-react-native-skills/rules/animation-derived-value.md +53 -0
- package/skills/vercel-react-native-skills/rules/animation-gesture-detector-press.md +95 -0
- package/skills/vercel-react-native-skills/rules/animation-gpu-properties.md +65 -0
- package/skills/vercel-react-native-skills/rules/design-system-compound-components.md +66 -0
- package/skills/vercel-react-native-skills/rules/fonts-config-plugin.md +71 -0
- package/skills/vercel-react-native-skills/rules/imports-design-system-folder.md +68 -0
- package/skills/vercel-react-native-skills/rules/js-hoist-intl.md +61 -0
- package/skills/vercel-react-native-skills/rules/list-performance-callbacks.md +44 -0
- package/skills/vercel-react-native-skills/rules/list-performance-function-references.md +132 -0
- package/skills/vercel-react-native-skills/rules/list-performance-images.md +53 -0
- package/skills/vercel-react-native-skills/rules/list-performance-inline-objects.md +97 -0
- package/skills/vercel-react-native-skills/rules/list-performance-item-expensive.md +94 -0
- package/skills/vercel-react-native-skills/rules/list-performance-item-memo.md +82 -0
- package/skills/vercel-react-native-skills/rules/list-performance-item-types.md +104 -0
- package/skills/vercel-react-native-skills/rules/list-performance-virtualize.md +67 -0
- package/skills/vercel-react-native-skills/rules/monorepo-native-deps-in-app.md +46 -0
- package/skills/vercel-react-native-skills/rules/monorepo-single-dependency-versions.md +63 -0
- package/skills/vercel-react-native-skills/rules/navigation-native-navigators.md +188 -0
- package/skills/vercel-react-native-skills/rules/react-compiler-destructure-functions.md +50 -0
- package/skills/vercel-react-native-skills/rules/react-compiler-reanimated-shared-values.md +48 -0
- package/skills/vercel-react-native-skills/rules/react-state-dispatcher.md +91 -0
- package/skills/vercel-react-native-skills/rules/react-state-fallback.md +56 -0
- package/skills/vercel-react-native-skills/rules/react-state-minimize.md +65 -0
- package/skills/vercel-react-native-skills/rules/rendering-no-falsy-and.md +74 -0
- package/skills/vercel-react-native-skills/rules/rendering-text-in-text-component.md +36 -0
- package/skills/vercel-react-native-skills/rules/scroll-position-no-state.md +82 -0
- package/skills/vercel-react-native-skills/rules/state-ground-truth.md +80 -0
- package/skills/vercel-react-native-skills/rules/ui-expo-image.md +66 -0
- package/skills/vercel-react-native-skills/rules/ui-image-gallery.md +104 -0
- package/skills/vercel-react-native-skills/rules/ui-measure-views.md +78 -0
- package/skills/vercel-react-native-skills/rules/ui-menus.md +174 -0
- package/skills/vercel-react-native-skills/rules/ui-native-modals.md +77 -0
- package/skills/vercel-react-native-skills/rules/ui-pressable.md +61 -0
- package/skills/vercel-react-native-skills/rules/ui-safe-area-scroll.md +65 -0
- package/skills/vercel-react-native-skills/rules/ui-scrollview-content-inset.md +45 -0
- package/skills/vercel-react-native-skills/rules/ui-styling.md +87 -0
- package/skills/web-design-guidelines/SKILL.md +39 -0
- package/templates/AGENTS.md +31 -0
- package/templates/CLAUDE.md +31 -0
|
@@ -0,0 +1,287 @@
|
|
|
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
|
+
## File Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
app/
|
|
9
|
+
├── @modal/ # Parallel route slot
|
|
10
|
+
│ ├── default.tsx # Required! Returns null
|
|
11
|
+
│ ├── (.)photos/ # Intercepts /photos/*
|
|
12
|
+
│ │ └── [id]/
|
|
13
|
+
│ │ └── page.tsx # Modal content
|
|
14
|
+
│ └── [...]catchall/ # Optional: catch unmatched
|
|
15
|
+
│ └── page.tsx
|
|
16
|
+
├── photos/
|
|
17
|
+
│ └── [id]/
|
|
18
|
+
│ └── page.tsx # Full page (direct access)
|
|
19
|
+
├── layout.tsx # Renders both children and @modal
|
|
20
|
+
└── page.tsx
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Step 1: Root Layout with Slot
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
// app/layout.tsx
|
|
27
|
+
export default function RootLayout({
|
|
28
|
+
children,
|
|
29
|
+
modal,
|
|
30
|
+
}: {
|
|
31
|
+
children: React.ReactNode;
|
|
32
|
+
modal: React.ReactNode;
|
|
33
|
+
}) {
|
|
34
|
+
return (
|
|
35
|
+
<html>
|
|
36
|
+
<body>
|
|
37
|
+
{children}
|
|
38
|
+
{modal}
|
|
39
|
+
</body>
|
|
40
|
+
</html>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Step 2: Default File (Critical!)
|
|
46
|
+
|
|
47
|
+
**Every parallel route slot MUST have a `default.tsx`** to prevent 404s on hard navigation.
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
// app/@modal/default.tsx
|
|
51
|
+
export default function Default() {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Without this file, refreshing any page will 404 because Next.js can't determine what to render in the `@modal` slot.
|
|
57
|
+
|
|
58
|
+
## Step 3: Intercepting Route (Modal)
|
|
59
|
+
|
|
60
|
+
The `(.)` prefix intercepts routes at the same level.
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
// app/@modal/(.)photos/[id]/page.tsx
|
|
64
|
+
import { Modal } from '@/components/modal';
|
|
65
|
+
|
|
66
|
+
export default async function PhotoModal({
|
|
67
|
+
params
|
|
68
|
+
}: {
|
|
69
|
+
params: Promise<{ id: string }>
|
|
70
|
+
}) {
|
|
71
|
+
const { id } = await params;
|
|
72
|
+
const photo = await getPhoto(id);
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<Modal>
|
|
76
|
+
<img src={photo.url} alt={photo.title} />
|
|
77
|
+
</Modal>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Step 4: Full Page (Direct Access)
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
// app/photos/[id]/page.tsx
|
|
86
|
+
export default async function PhotoPage({
|
|
87
|
+
params
|
|
88
|
+
}: {
|
|
89
|
+
params: Promise<{ id: string }>
|
|
90
|
+
}) {
|
|
91
|
+
const { id } = await params;
|
|
92
|
+
const photo = await getPhoto(id);
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<div className="full-page">
|
|
96
|
+
<img src={photo.url} alt={photo.title} />
|
|
97
|
+
<h1>{photo.title}</h1>
|
|
98
|
+
</div>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Step 5: Modal Component with Correct Closing
|
|
104
|
+
|
|
105
|
+
**Critical: Use `router.back()` to close modals, NOT `router.push()` or `<Link>`.**
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
// components/modal.tsx
|
|
109
|
+
'use client';
|
|
110
|
+
|
|
111
|
+
import { useRouter } from 'next/navigation';
|
|
112
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
113
|
+
|
|
114
|
+
export function Modal({ children }: { children: React.ReactNode }) {
|
|
115
|
+
const router = useRouter();
|
|
116
|
+
const overlayRef = useRef<HTMLDivElement>(null);
|
|
117
|
+
|
|
118
|
+
// Close on escape key
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
function onKeyDown(e: KeyboardEvent) {
|
|
121
|
+
if (e.key === 'Escape') {
|
|
122
|
+
router.back(); // Correct
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
document.addEventListener('keydown', onKeyDown);
|
|
126
|
+
return () => document.removeEventListener('keydown', onKeyDown);
|
|
127
|
+
}, [router]);
|
|
128
|
+
|
|
129
|
+
// Close on overlay click
|
|
130
|
+
const handleOverlayClick = useCallback((e: React.MouseEvent) => {
|
|
131
|
+
if (e.target === overlayRef.current) {
|
|
132
|
+
router.back(); // Correct
|
|
133
|
+
}
|
|
134
|
+
}, [router]);
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<div
|
|
138
|
+
ref={overlayRef}
|
|
139
|
+
onClick={handleOverlayClick}
|
|
140
|
+
className="fixed inset-0 bg-black/50 flex items-center justify-center z-50"
|
|
141
|
+
>
|
|
142
|
+
<div className="bg-white rounded-lg p-6 max-w-2xl w-full mx-4">
|
|
143
|
+
<button
|
|
144
|
+
onClick={() => router.back()} // Correct!
|
|
145
|
+
className="absolute top-4 right-4"
|
|
146
|
+
>
|
|
147
|
+
Close
|
|
148
|
+
</button>
|
|
149
|
+
{children}
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Why NOT `router.push('/')` or `<Link href="/">`?
|
|
157
|
+
|
|
158
|
+
Using `push` or `Link` to "close" a modal:
|
|
159
|
+
1. Adds a new history entry (back button shows modal again)
|
|
160
|
+
2. Doesn't properly clear the intercepted route
|
|
161
|
+
3. Can cause the modal to flash or persist unexpectedly
|
|
162
|
+
|
|
163
|
+
`router.back()` correctly:
|
|
164
|
+
1. Removes the intercepted route from history
|
|
165
|
+
2. Returns to the previous page
|
|
166
|
+
3. Properly unmounts the modal
|
|
167
|
+
|
|
168
|
+
## Route Matcher Reference
|
|
169
|
+
|
|
170
|
+
Matchers match **route segments**, not filesystem paths:
|
|
171
|
+
|
|
172
|
+
| Matcher | Matches | Example |
|
|
173
|
+
|---------|---------|---------|
|
|
174
|
+
| `(.)` | Same level | `@modal/(.)photos` intercepts `/photos` |
|
|
175
|
+
| `(..)` | One level up | `@modal/(..)settings` from `/dashboard/@modal` intercepts `/settings` |
|
|
176
|
+
| `(..)(..)` | Two levels up | Rarely used |
|
|
177
|
+
| `(...)` | From root | `@modal/(...)photos` intercepts `/photos` from anywhere |
|
|
178
|
+
|
|
179
|
+
**Common mistake**: Thinking `(..)` means "parent folder" - it means "parent route segment".
|
|
180
|
+
|
|
181
|
+
## Handling Hard Navigation
|
|
182
|
+
|
|
183
|
+
When users directly visit `/photos/123` (bookmark, refresh, shared link):
|
|
184
|
+
- The intercepting route is bypassed
|
|
185
|
+
- The full `photos/[id]/page.tsx` renders
|
|
186
|
+
- Modal doesn't appear (expected behavior)
|
|
187
|
+
|
|
188
|
+
If you want the modal to appear on direct access too, you need additional logic:
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
// app/photos/[id]/page.tsx
|
|
192
|
+
import { Modal } from '@/components/modal';
|
|
193
|
+
|
|
194
|
+
export default async function PhotoPage({ params }) {
|
|
195
|
+
const { id } = await params;
|
|
196
|
+
const photo = await getPhoto(id);
|
|
197
|
+
|
|
198
|
+
// Option: Render as modal on direct access too
|
|
199
|
+
return (
|
|
200
|
+
<Modal>
|
|
201
|
+
<img src={photo.url} alt={photo.title} />
|
|
202
|
+
</Modal>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Common Gotchas
|
|
208
|
+
|
|
209
|
+
### 1. Missing `default.tsx` → 404 on Refresh
|
|
210
|
+
|
|
211
|
+
Every `@slot` folder needs a `default.tsx` that returns `null` (or appropriate content).
|
|
212
|
+
|
|
213
|
+
### 2. Modal Persists After Navigation
|
|
214
|
+
|
|
215
|
+
You're using `router.push()` instead of `router.back()`.
|
|
216
|
+
|
|
217
|
+
### 3. Nested Parallel Routes Need Defaults Too
|
|
218
|
+
|
|
219
|
+
If you have `@modal` inside a route group, each level needs its own `default.tsx`:
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
app/
|
|
223
|
+
├── (marketing)/
|
|
224
|
+
│ ├── @modal/
|
|
225
|
+
│ │ └── default.tsx # Needed!
|
|
226
|
+
│ └── layout.tsx
|
|
227
|
+
└── layout.tsx
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### 4. Intercepted Route Shows Wrong Content
|
|
231
|
+
|
|
232
|
+
Check your matcher:
|
|
233
|
+
- `(.)photos` intercepts `/photos` from the same route level
|
|
234
|
+
- If your `@modal` is in `app/dashboard/@modal`, use `(.)photos` to intercept `/dashboard/photos`, not `/photos`
|
|
235
|
+
|
|
236
|
+
### 5. TypeScript Errors with `params`
|
|
237
|
+
|
|
238
|
+
In Next.js 15+, `params` is a Promise:
|
|
239
|
+
|
|
240
|
+
```tsx
|
|
241
|
+
// Correct
|
|
242
|
+
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
|
|
243
|
+
const { id } = await params;
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Complete Example: Photo Gallery Modal
|
|
248
|
+
|
|
249
|
+
```
|
|
250
|
+
app/
|
|
251
|
+
├── @modal/
|
|
252
|
+
│ ├── default.tsx
|
|
253
|
+
│ └── (.)photos/
|
|
254
|
+
│ └── [id]/
|
|
255
|
+
│ └── page.tsx
|
|
256
|
+
├── photos/
|
|
257
|
+
│ ├── page.tsx # Gallery grid
|
|
258
|
+
│ └── [id]/
|
|
259
|
+
│ └── page.tsx # Full photo page
|
|
260
|
+
├── layout.tsx
|
|
261
|
+
└── page.tsx
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Links in the gallery:
|
|
265
|
+
|
|
266
|
+
```tsx
|
|
267
|
+
// app/photos/page.tsx
|
|
268
|
+
import Link from 'next/link';
|
|
269
|
+
|
|
270
|
+
export default async function Gallery() {
|
|
271
|
+
const photos = await getPhotos();
|
|
272
|
+
|
|
273
|
+
return (
|
|
274
|
+
<div className="grid grid-cols-3 gap-4">
|
|
275
|
+
{photos.map(photo => (
|
|
276
|
+
<Link key={photo.id} href={`/photos/${photo.id}`}>
|
|
277
|
+
<img src={photo.thumbnail} alt={photo.title} />
|
|
278
|
+
</Link>
|
|
279
|
+
))}
|
|
280
|
+
</div>
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Clicking a photo → Modal opens (intercepted)
|
|
286
|
+
Direct URL → Full page renders
|
|
287
|
+
Refresh while modal open → Full page renders
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Route Handlers
|
|
2
|
+
|
|
3
|
+
Create API endpoints with `route.ts` files.
|
|
4
|
+
|
|
5
|
+
## Basic Usage
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
// app/api/users/route.ts
|
|
9
|
+
export async function GET() {
|
|
10
|
+
const users = await getUsers()
|
|
11
|
+
return Response.json(users)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function POST(request: Request) {
|
|
15
|
+
const body = await request.json()
|
|
16
|
+
const user = await createUser(body)
|
|
17
|
+
return Response.json(user, { status: 201 })
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Supported Methods
|
|
22
|
+
|
|
23
|
+
`GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`
|
|
24
|
+
|
|
25
|
+
## GET Handler Conflicts with page.tsx
|
|
26
|
+
|
|
27
|
+
**A `route.ts` and `page.tsx` cannot coexist in the same folder.**
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
app/
|
|
31
|
+
├── api/
|
|
32
|
+
│ └── users/
|
|
33
|
+
│ └── route.ts # /api/users
|
|
34
|
+
└── users/
|
|
35
|
+
├── page.tsx # /users (page)
|
|
36
|
+
└── route.ts # Warning: Conflicts with page.tsx!
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
If you need both a page and an API at the same path, use different paths:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
app/
|
|
43
|
+
├── users/
|
|
44
|
+
│ └── page.tsx # /users (page)
|
|
45
|
+
└── api/
|
|
46
|
+
└── users/
|
|
47
|
+
└── route.ts # /api/users (API)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Environment Behavior
|
|
51
|
+
|
|
52
|
+
Route handlers run in a **Server Component-like environment**:
|
|
53
|
+
|
|
54
|
+
- Yes: Can use `async/await`
|
|
55
|
+
- Yes: Can access `cookies()`, `headers()`
|
|
56
|
+
- Yes: Can use Node.js APIs
|
|
57
|
+
- No: Cannot use React hooks
|
|
58
|
+
- No: Cannot use React DOM APIs
|
|
59
|
+
- No: Cannot use browser APIs
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
// Bad: This won't work - no React DOM in route handlers
|
|
63
|
+
import { renderToString } from 'react-dom/server'
|
|
64
|
+
|
|
65
|
+
export async function GET() {
|
|
66
|
+
const html = renderToString(<Component />) // Error!
|
|
67
|
+
return new Response(html)
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Dynamic Route Handlers
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
// app/api/users/[id]/route.ts
|
|
75
|
+
export async function GET(
|
|
76
|
+
request: Request,
|
|
77
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
78
|
+
) {
|
|
79
|
+
const { id } = await params
|
|
80
|
+
const user = await getUser(id)
|
|
81
|
+
|
|
82
|
+
if (!user) {
|
|
83
|
+
return Response.json({ error: 'Not found' }, { status: 404 })
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return Response.json(user)
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Request Helpers
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
export async function GET(request: Request) {
|
|
94
|
+
// URL and search params
|
|
95
|
+
const { searchParams } = new URL(request.url)
|
|
96
|
+
const query = searchParams.get('q')
|
|
97
|
+
|
|
98
|
+
// Headers
|
|
99
|
+
const authHeader = request.headers.get('authorization')
|
|
100
|
+
|
|
101
|
+
// Cookies (Next.js helper)
|
|
102
|
+
const cookieStore = await cookies()
|
|
103
|
+
const token = cookieStore.get('token')
|
|
104
|
+
|
|
105
|
+
return Response.json({ query, token })
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Response Helpers
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
// JSON response
|
|
113
|
+
return Response.json({ data })
|
|
114
|
+
|
|
115
|
+
// With status
|
|
116
|
+
return Response.json({ error: 'Not found' }, { status: 404 })
|
|
117
|
+
|
|
118
|
+
// With headers
|
|
119
|
+
return Response.json(data, {
|
|
120
|
+
headers: {
|
|
121
|
+
'Cache-Control': 'max-age=3600',
|
|
122
|
+
},
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
// Redirect
|
|
126
|
+
return Response.redirect(new URL('/login', request.url))
|
|
127
|
+
|
|
128
|
+
// Stream
|
|
129
|
+
return new Response(stream, {
|
|
130
|
+
headers: { 'Content-Type': 'text/event-stream' },
|
|
131
|
+
})
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## When to Use Route Handlers vs Server Actions
|
|
135
|
+
|
|
136
|
+
| Use Case | Route Handlers | Server Actions |
|
|
137
|
+
|----------|----------------|----------------|
|
|
138
|
+
| Form submissions | No | Yes |
|
|
139
|
+
| Data mutations from UI | No | Yes |
|
|
140
|
+
| Third-party webhooks | Yes | No |
|
|
141
|
+
| External API consumption | Yes | No |
|
|
142
|
+
| Public REST API | Yes | No |
|
|
143
|
+
| File uploads | Both work | Both work |
|
|
144
|
+
|
|
145
|
+
**Prefer Server Actions** for mutations triggered from your UI.
|
|
146
|
+
**Use Route Handlers** for external integrations and public APIs.
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# RSC Boundaries
|
|
2
|
+
|
|
3
|
+
Detect and prevent invalid patterns when crossing Server/Client component boundaries.
|
|
4
|
+
|
|
5
|
+
## Detection Rules
|
|
6
|
+
|
|
7
|
+
### 1. Async Client Components Are Invalid
|
|
8
|
+
|
|
9
|
+
Client components **cannot** be async functions. Only Server Components can be async.
|
|
10
|
+
|
|
11
|
+
**Detect:** File has `'use client'` AND component is `async function` or returns `Promise`
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
// Bad: async client component
|
|
15
|
+
'use client'
|
|
16
|
+
export default async function UserProfile() {
|
|
17
|
+
const user = await getUser() // Cannot await in client component
|
|
18
|
+
return <div>{user.name}</div>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Good: Remove async, fetch data in parent server component
|
|
22
|
+
// page.tsx (server component - no 'use client')
|
|
23
|
+
export default async function Page() {
|
|
24
|
+
const user = await getUser()
|
|
25
|
+
return <UserProfile user={user} />
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// UserProfile.tsx (client component)
|
|
29
|
+
'use client'
|
|
30
|
+
export function UserProfile({ user }: { user: User }) {
|
|
31
|
+
return <div>{user.name}</div>
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
// Bad: async arrow function client component
|
|
37
|
+
'use client'
|
|
38
|
+
const Dashboard = async () => {
|
|
39
|
+
const data = await fetchDashboard()
|
|
40
|
+
return <div>{data}</div>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Good: Fetch in server component, pass data down
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 2. Non-Serializable Props to Client Components
|
|
47
|
+
|
|
48
|
+
Props passed from Server → Client must be JSON-serializable.
|
|
49
|
+
|
|
50
|
+
**Detect:** Server component passes these to a client component:
|
|
51
|
+
- Functions (except Server Actions with `'use server'`)
|
|
52
|
+
- `Date` objects
|
|
53
|
+
- `Map`, `Set`, `WeakMap`, `WeakSet`
|
|
54
|
+
- Class instances
|
|
55
|
+
- `Symbol` (unless globally registered)
|
|
56
|
+
- Circular references
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
// Bad: Function prop
|
|
60
|
+
// page.tsx (server)
|
|
61
|
+
export default function Page() {
|
|
62
|
+
const handleClick = () => console.log('clicked')
|
|
63
|
+
return <ClientButton onClick={handleClick} />
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Good: Define function inside client component
|
|
67
|
+
// ClientButton.tsx
|
|
68
|
+
'use client'
|
|
69
|
+
export function ClientButton() {
|
|
70
|
+
const handleClick = () => console.log('clicked')
|
|
71
|
+
return <button onClick={handleClick}>Click</button>
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
// Bad: Date object (silently becomes string, then crashes)
|
|
77
|
+
// page.tsx (server)
|
|
78
|
+
export default async function Page() {
|
|
79
|
+
const post = await getPost()
|
|
80
|
+
return <PostCard createdAt={post.createdAt} /> // Date object
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// PostCard.tsx (client) - will crash on .getFullYear()
|
|
84
|
+
'use client'
|
|
85
|
+
export function PostCard({ createdAt }: { createdAt: Date }) {
|
|
86
|
+
return <span>{createdAt.getFullYear()}</span> // Runtime error!
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Good: Serialize to string on server
|
|
90
|
+
// page.tsx (server)
|
|
91
|
+
export default async function Page() {
|
|
92
|
+
const post = await getPost()
|
|
93
|
+
return <PostCard createdAt={post.createdAt.toISOString()} />
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// PostCard.tsx (client)
|
|
97
|
+
'use client'
|
|
98
|
+
export function PostCard({ createdAt }: { createdAt: string }) {
|
|
99
|
+
const date = new Date(createdAt)
|
|
100
|
+
return <span>{date.getFullYear()}</span>
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
// Bad: Class instance
|
|
106
|
+
const user = new UserModel(data)
|
|
107
|
+
<ClientProfile user={user} /> // Methods will be stripped
|
|
108
|
+
|
|
109
|
+
// Good: Pass plain object
|
|
110
|
+
const user = await getUser()
|
|
111
|
+
<ClientProfile user={{ id: user.id, name: user.name }} />
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
// Bad: Map/Set
|
|
116
|
+
<ClientComponent items={new Map([['a', 1]])} />
|
|
117
|
+
|
|
118
|
+
// Good: Convert to array/object
|
|
119
|
+
<ClientComponent items={Object.fromEntries(map)} />
|
|
120
|
+
<ClientComponent items={Array.from(set)} />
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 3. Server Actions Are the Exception
|
|
124
|
+
|
|
125
|
+
Functions marked with `'use server'` CAN be passed to client components.
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
// Valid: Server Action can be passed
|
|
129
|
+
// actions.ts
|
|
130
|
+
'use server'
|
|
131
|
+
export async function submitForm(formData: FormData) {
|
|
132
|
+
// server-side logic
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// page.tsx (server)
|
|
136
|
+
import { submitForm } from './actions'
|
|
137
|
+
export default function Page() {
|
|
138
|
+
return <ClientForm onSubmit={submitForm} /> // OK!
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ClientForm.tsx (client)
|
|
142
|
+
'use client'
|
|
143
|
+
export function ClientForm({ onSubmit }: { onSubmit: (data: FormData) => Promise<void> }) {
|
|
144
|
+
return <form action={onSubmit}>...</form>
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Quick Reference
|
|
149
|
+
|
|
150
|
+
| Pattern | Valid? | Fix |
|
|
151
|
+
|---------|--------|-----|
|
|
152
|
+
| `'use client'` + `async function` | No | Fetch in server parent, pass data |
|
|
153
|
+
| Pass `() => {}` to client | No | Define in client or use server action |
|
|
154
|
+
| Pass `new Date()` to client | No | Use `.toISOString()` |
|
|
155
|
+
| Pass `new Map()` to client | No | Convert to object/array |
|
|
156
|
+
| Pass class instance to client | No | Pass plain object |
|
|
157
|
+
| Pass server action to client | Yes | - |
|
|
158
|
+
| Pass `string/number/boolean` | Yes | - |
|
|
159
|
+
| Pass plain object/array | Yes | - |
|
|
@@ -0,0 +1,39 @@
|
|
|
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
|
+
1. Does the project already use Edge runtime?
|
|
36
|
+
2. Is there a specific latency requirement?
|
|
37
|
+
3. Are all dependencies Edge-compatible?
|
|
38
|
+
|
|
39
|
+
If unsure, use Node.js runtime.
|