@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,173 @@
|
|
|
1
|
+
# Image Optimization
|
|
2
|
+
|
|
3
|
+
Use `next/image` for automatic image optimization.
|
|
4
|
+
|
|
5
|
+
## Always Use next/image
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
// Bad: Avoid native img
|
|
9
|
+
<img src="/hero.png" alt="Hero" />
|
|
10
|
+
|
|
11
|
+
// Good: Use next/image
|
|
12
|
+
import Image from 'next/image'
|
|
13
|
+
<Image src="/hero.png" alt="Hero" width={800} height={400} />
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Required Props
|
|
17
|
+
|
|
18
|
+
Images need explicit dimensions to prevent layout shift:
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
// Local images - dimensions inferred automatically
|
|
22
|
+
import heroImage from './hero.png'
|
|
23
|
+
<Image src={heroImage} alt="Hero" />
|
|
24
|
+
|
|
25
|
+
// Remote images - must specify width/height
|
|
26
|
+
<Image src="https://example.com/image.jpg" alt="Hero" width={800} height={400} />
|
|
27
|
+
|
|
28
|
+
// Or use fill for parent-relative sizing
|
|
29
|
+
<div style={{ position: 'relative', width: '100%', height: 400 }}>
|
|
30
|
+
<Image src="/hero.png" alt="Hero" fill style={{ objectFit: 'cover' }} />
|
|
31
|
+
</div>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Remote Images Configuration
|
|
35
|
+
|
|
36
|
+
Remote domains must be configured in `next.config.js`:
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
// next.config.js
|
|
40
|
+
module.exports = {
|
|
41
|
+
images: {
|
|
42
|
+
remotePatterns: [
|
|
43
|
+
{
|
|
44
|
+
protocol: 'https',
|
|
45
|
+
hostname: 'example.com',
|
|
46
|
+
pathname: '/images/**',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
protocol: 'https',
|
|
50
|
+
hostname: '*.cdn.com', // Wildcard subdomain
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Responsive Images
|
|
58
|
+
|
|
59
|
+
Use `sizes` to tell the browser which size to download:
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
// Full-width hero
|
|
63
|
+
<Image
|
|
64
|
+
src="/hero.png"
|
|
65
|
+
alt="Hero"
|
|
66
|
+
fill
|
|
67
|
+
sizes="100vw"
|
|
68
|
+
/>
|
|
69
|
+
|
|
70
|
+
// Responsive grid (3 columns on desktop, 1 on mobile)
|
|
71
|
+
<Image
|
|
72
|
+
src="/card.png"
|
|
73
|
+
alt="Card"
|
|
74
|
+
fill
|
|
75
|
+
sizes="(max-width: 768px) 100vw, 33vw"
|
|
76
|
+
/>
|
|
77
|
+
|
|
78
|
+
// Fixed sidebar image
|
|
79
|
+
<Image
|
|
80
|
+
src="/avatar.png"
|
|
81
|
+
alt="Avatar"
|
|
82
|
+
width={200}
|
|
83
|
+
height={200}
|
|
84
|
+
sizes="200px"
|
|
85
|
+
/>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Blur Placeholder
|
|
89
|
+
|
|
90
|
+
Prevent layout shift with placeholders:
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
// Local images - automatic blur hash
|
|
94
|
+
import heroImage from './hero.png'
|
|
95
|
+
<Image src={heroImage} alt="Hero" placeholder="blur" />
|
|
96
|
+
|
|
97
|
+
// Remote images - provide blurDataURL
|
|
98
|
+
<Image
|
|
99
|
+
src="https://example.com/image.jpg"
|
|
100
|
+
alt="Hero"
|
|
101
|
+
width={800}
|
|
102
|
+
height={400}
|
|
103
|
+
placeholder="blur"
|
|
104
|
+
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRg..."
|
|
105
|
+
/>
|
|
106
|
+
|
|
107
|
+
// Or use color placeholder
|
|
108
|
+
<Image
|
|
109
|
+
src="https://example.com/image.jpg"
|
|
110
|
+
alt="Hero"
|
|
111
|
+
width={800}
|
|
112
|
+
height={400}
|
|
113
|
+
placeholder="empty"
|
|
114
|
+
style={{ backgroundColor: '#e0e0e0' }}
|
|
115
|
+
/>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Priority Loading
|
|
119
|
+
|
|
120
|
+
Use `priority` for above-the-fold images (LCP):
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
// Hero image - loads immediately
|
|
124
|
+
<Image src="/hero.png" alt="Hero" fill priority />
|
|
125
|
+
|
|
126
|
+
// Below-fold images - lazy loaded by default (no priority needed)
|
|
127
|
+
<Image src="/card.png" alt="Card" width={400} height={300} />
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Common Mistakes
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
// Bad: Missing sizes with fill - downloads largest image
|
|
134
|
+
<Image src="/hero.png" alt="Hero" fill />
|
|
135
|
+
|
|
136
|
+
// Good: Add sizes for proper responsive behavior
|
|
137
|
+
<Image src="/hero.png" alt="Hero" fill sizes="100vw" />
|
|
138
|
+
|
|
139
|
+
// Bad: Using width/height for aspect ratio only
|
|
140
|
+
<Image src="/hero.png" alt="Hero" width={16} height={9} />
|
|
141
|
+
|
|
142
|
+
// Good: Use actual display dimensions or fill with sizes
|
|
143
|
+
<Image src="/hero.png" alt="Hero" fill sizes="100vw" style={{ objectFit: 'cover' }} />
|
|
144
|
+
|
|
145
|
+
// Bad: Remote image without config
|
|
146
|
+
<Image src="https://untrusted.com/image.jpg" alt="Image" width={400} height={300} />
|
|
147
|
+
// Error: Invalid src prop, hostname not configured
|
|
148
|
+
|
|
149
|
+
// Good: Add hostname to next.config.js remotePatterns
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Static Export
|
|
153
|
+
|
|
154
|
+
When using `output: 'export'`, use `unoptimized` or custom loader:
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
// Option 1: Disable optimization
|
|
158
|
+
<Image src="/hero.png" alt="Hero" width={800} height={400} unoptimized />
|
|
159
|
+
|
|
160
|
+
// Option 2: Global config
|
|
161
|
+
// next.config.js
|
|
162
|
+
module.exports = {
|
|
163
|
+
output: 'export',
|
|
164
|
+
images: { unoptimized: true },
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Option 3: Custom loader (Cloudinary, Imgix, etc.)
|
|
168
|
+
const cloudinaryLoader = ({ src, width, quality }) => {
|
|
169
|
+
return `https://res.cloudinary.com/demo/image/upload/w_${width},q_${quality || 75}/${src}`
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
<Image loader={cloudinaryLoader} src="sample.jpg" alt="Sample" width={800} height={400} />
|
|
173
|
+
```
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# Metadata
|
|
2
|
+
|
|
3
|
+
Add SEO metadata to Next.js pages using the Metadata API.
|
|
4
|
+
|
|
5
|
+
## Important: Server Components Only
|
|
6
|
+
|
|
7
|
+
The `metadata` object and `generateMetadata` function are **only supported in Server Components**. They cannot be used in Client Components.
|
|
8
|
+
|
|
9
|
+
If the target page has `'use client'`:
|
|
10
|
+
1. Remove `'use client'` if possible, move client logic to child components
|
|
11
|
+
2. Or extract metadata to a parent Server Component layout
|
|
12
|
+
3. Or split the file: Server Component with metadata imports Client Components
|
|
13
|
+
|
|
14
|
+
## Static Metadata
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import type { Metadata } from 'next'
|
|
18
|
+
|
|
19
|
+
export const metadata: Metadata = {
|
|
20
|
+
title: 'Page Title',
|
|
21
|
+
description: 'Page description for search engines',
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Dynamic Metadata
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import type { Metadata } from 'next'
|
|
29
|
+
|
|
30
|
+
type Props = { params: Promise<{ slug: string }> }
|
|
31
|
+
|
|
32
|
+
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
|
33
|
+
const { slug } = await params
|
|
34
|
+
const post = await getPost(slug)
|
|
35
|
+
return { title: post.title, description: post.description }
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Avoid Duplicate Fetches
|
|
40
|
+
|
|
41
|
+
Use React `cache()` when the same data is needed for both metadata and page:
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import { cache } from 'react'
|
|
45
|
+
|
|
46
|
+
export const getPost = cache(async (slug: string) => {
|
|
47
|
+
return await db.posts.findFirst({ where: { slug } })
|
|
48
|
+
})
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Viewport
|
|
52
|
+
|
|
53
|
+
Separate from metadata for streaming support:
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
import type { Viewport } from 'next'
|
|
57
|
+
|
|
58
|
+
export const viewport: Viewport = {
|
|
59
|
+
width: 'device-width',
|
|
60
|
+
initialScale: 1,
|
|
61
|
+
themeColor: '#000000',
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Or dynamic
|
|
65
|
+
export function generateViewport({ params }): Viewport {
|
|
66
|
+
return { themeColor: getThemeColor(params) }
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Title Templates
|
|
71
|
+
|
|
72
|
+
In root layout for consistent naming:
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
export const metadata: Metadata = {
|
|
76
|
+
title: { default: 'Site Name', template: '%s | Site Name' },
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Metadata File Conventions
|
|
81
|
+
|
|
82
|
+
Reference: https://nextjs.org/docs/app/getting-started/project-structure#metadata-file-conventions
|
|
83
|
+
|
|
84
|
+
Place these files in `app/` directory (or route segments):
|
|
85
|
+
|
|
86
|
+
| File | Purpose |
|
|
87
|
+
|------|---------|
|
|
88
|
+
| `favicon.ico` | Favicon |
|
|
89
|
+
| `icon.png` / `icon.svg` | App icon |
|
|
90
|
+
| `apple-icon.png` | Apple app icon |
|
|
91
|
+
| `opengraph-image.png` | OG image |
|
|
92
|
+
| `twitter-image.png` | Twitter card image |
|
|
93
|
+
| `sitemap.ts` / `sitemap.xml` | Sitemap (use `generateSitemaps` for multiple) |
|
|
94
|
+
| `robots.ts` / `robots.txt` | Robots directives |
|
|
95
|
+
| `manifest.ts` / `manifest.json` | Web app manifest |
|
|
96
|
+
|
|
97
|
+
## SEO Best Practice: Static Files Are Often Enough
|
|
98
|
+
|
|
99
|
+
For most sites, **static metadata files provide excellent SEO coverage**:
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
app/
|
|
103
|
+
├── favicon.ico
|
|
104
|
+
├── opengraph-image.png # Works for both OG and Twitter
|
|
105
|
+
├── sitemap.ts
|
|
106
|
+
├── robots.ts
|
|
107
|
+
└── layout.tsx # With title/description metadata
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Tips:**
|
|
111
|
+
- A single `opengraph-image.png` covers both Open Graph and Twitter (Twitter falls back to OG)
|
|
112
|
+
- Static `title` and `description` in layout metadata is sufficient for most pages
|
|
113
|
+
- Only use dynamic `generateMetadata` when content varies per page
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
# OG Image Generation
|
|
118
|
+
|
|
119
|
+
Generate dynamic Open Graph images using `next/og`.
|
|
120
|
+
|
|
121
|
+
## Important Rules
|
|
122
|
+
|
|
123
|
+
1. **Use `next/og`** - not `@vercel/og` (it's built into Next.js)
|
|
124
|
+
2. **No searchParams** - OG images can't access search params, use route params instead
|
|
125
|
+
3. **Avoid Edge runtime** - Use default Node.js runtime
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
// Good
|
|
129
|
+
import { ImageResponse } from 'next/og'
|
|
130
|
+
|
|
131
|
+
// Bad
|
|
132
|
+
// import { ImageResponse } from '@vercel/og'
|
|
133
|
+
// export const runtime = 'edge'
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Basic OG Image
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
// app/opengraph-image.tsx
|
|
140
|
+
import { ImageResponse } from 'next/og'
|
|
141
|
+
|
|
142
|
+
export const alt = 'Site Name'
|
|
143
|
+
export const size = { width: 1200, height: 630 }
|
|
144
|
+
export const contentType = 'image/png'
|
|
145
|
+
|
|
146
|
+
export default function Image() {
|
|
147
|
+
return new ImageResponse(
|
|
148
|
+
(
|
|
149
|
+
<div
|
|
150
|
+
style={{
|
|
151
|
+
fontSize: 128,
|
|
152
|
+
background: 'white',
|
|
153
|
+
width: '100%',
|
|
154
|
+
height: '100%',
|
|
155
|
+
display: 'flex',
|
|
156
|
+
alignItems: 'center',
|
|
157
|
+
justifyContent: 'center',
|
|
158
|
+
}}
|
|
159
|
+
>
|
|
160
|
+
Hello World
|
|
161
|
+
</div>
|
|
162
|
+
),
|
|
163
|
+
{ ...size }
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Dynamic OG Image
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
// app/blog/[slug]/opengraph-image.tsx
|
|
172
|
+
import { ImageResponse } from 'next/og'
|
|
173
|
+
|
|
174
|
+
export const alt = 'Blog Post'
|
|
175
|
+
export const size = { width: 1200, height: 630 }
|
|
176
|
+
export const contentType = 'image/png'
|
|
177
|
+
|
|
178
|
+
type Props = { params: Promise<{ slug: string }> }
|
|
179
|
+
|
|
180
|
+
export default async function Image({ params }: Props) {
|
|
181
|
+
const { slug } = await params
|
|
182
|
+
const post = await getPost(slug)
|
|
183
|
+
|
|
184
|
+
return new ImageResponse(
|
|
185
|
+
(
|
|
186
|
+
<div
|
|
187
|
+
style={{
|
|
188
|
+
fontSize: 48,
|
|
189
|
+
background: 'linear-gradient(to bottom, #1a1a1a, #333)',
|
|
190
|
+
color: 'white',
|
|
191
|
+
width: '100%',
|
|
192
|
+
height: '100%',
|
|
193
|
+
display: 'flex',
|
|
194
|
+
flexDirection: 'column',
|
|
195
|
+
alignItems: 'center',
|
|
196
|
+
justifyContent: 'center',
|
|
197
|
+
padding: 48,
|
|
198
|
+
}}
|
|
199
|
+
>
|
|
200
|
+
<div style={{ fontSize: 64, fontWeight: 'bold' }}>{post.title}</div>
|
|
201
|
+
<div style={{ marginTop: 24, opacity: 0.8 }}>{post.description}</div>
|
|
202
|
+
</div>
|
|
203
|
+
),
|
|
204
|
+
{ ...size }
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Custom Fonts
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
import { ImageResponse } from 'next/og'
|
|
213
|
+
import { join } from 'path'
|
|
214
|
+
import { readFile } from 'fs/promises'
|
|
215
|
+
|
|
216
|
+
export default async function Image() {
|
|
217
|
+
const fontPath = join(process.cwd(), 'assets/fonts/Inter-Bold.ttf')
|
|
218
|
+
const fontData = await readFile(fontPath)
|
|
219
|
+
|
|
220
|
+
return new ImageResponse(
|
|
221
|
+
(
|
|
222
|
+
<div style={{ fontFamily: 'Inter', fontSize: 64 }}>
|
|
223
|
+
Custom Font Text
|
|
224
|
+
</div>
|
|
225
|
+
),
|
|
226
|
+
{
|
|
227
|
+
width: 1200,
|
|
228
|
+
height: 630,
|
|
229
|
+
fonts: [{ name: 'Inter', data: fontData, style: 'normal' }],
|
|
230
|
+
}
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## File Naming
|
|
236
|
+
|
|
237
|
+
- `opengraph-image.tsx` - Open Graph (Facebook, LinkedIn)
|
|
238
|
+
- `twitter-image.tsx` - Twitter/X cards (optional, falls back to OG)
|
|
239
|
+
|
|
240
|
+
## Styling Notes
|
|
241
|
+
|
|
242
|
+
ImageResponse uses Flexbox layout:
|
|
243
|
+
- Use `display: 'flex'`
|
|
244
|
+
- No CSS Grid support
|
|
245
|
+
- Styles must be inline objects
|
|
246
|
+
|
|
247
|
+
## Multiple OG Images
|
|
248
|
+
|
|
249
|
+
Use `generateImageMetadata` for multiple images per route:
|
|
250
|
+
|
|
251
|
+
```tsx
|
|
252
|
+
// app/blog/[slug]/opengraph-image.tsx
|
|
253
|
+
import { ImageResponse } from 'next/og'
|
|
254
|
+
|
|
255
|
+
export async function generateImageMetadata({ params }) {
|
|
256
|
+
const images = await getPostImages(params.slug)
|
|
257
|
+
return images.map((img, idx) => ({
|
|
258
|
+
id: idx,
|
|
259
|
+
alt: img.alt,
|
|
260
|
+
size: { width: 1200, height: 630 },
|
|
261
|
+
contentType: 'image/png',
|
|
262
|
+
}))
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export default async function Image({ params, id }) {
|
|
266
|
+
const images = await getPostImages(params.slug)
|
|
267
|
+
const image = images[id]
|
|
268
|
+
return new ImageResponse(/* ... */)
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Multiple Sitemaps
|
|
273
|
+
|
|
274
|
+
Use `generateSitemaps` for large sites:
|
|
275
|
+
|
|
276
|
+
```tsx
|
|
277
|
+
// app/sitemap.ts
|
|
278
|
+
import type { MetadataRoute } from 'next'
|
|
279
|
+
|
|
280
|
+
export async function generateSitemaps() {
|
|
281
|
+
// Return array of sitemap IDs
|
|
282
|
+
return [{ id: 0 }, { id: 1 }, { id: 2 }]
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export default async function sitemap({
|
|
286
|
+
id,
|
|
287
|
+
}: {
|
|
288
|
+
id: number
|
|
289
|
+
}): Promise<MetadataRoute.Sitemap> {
|
|
290
|
+
const start = id * 50000
|
|
291
|
+
const end = start + 50000
|
|
292
|
+
const products = await getProducts(start, end)
|
|
293
|
+
|
|
294
|
+
return products.map((product) => ({
|
|
295
|
+
url: `https://example.com/product/${product.id}`,
|
|
296
|
+
lastModified: product.updatedAt,
|
|
297
|
+
}))
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Generates `/sitemap/0.xml`, `/sitemap/1.xml`, etc.
|