create-baton 1.0.0 → 1.1.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.
@@ -0,0 +1,219 @@
1
+ ---
2
+ name: seo
3
+ description: >-
4
+ SEO patterns — metadata, Open Graph, sitemaps, structured data, and
5
+ Core Web Vitals. Load at Session 5+ when preparing for launch.
6
+ Use when optimizing pages for search engines or social sharing.
7
+ ---
8
+
9
+ # SEO Skill
10
+
11
+ > SEO is not magic. It's metadata + performance + content. Do the basics right.
12
+
13
+ ---
14
+
15
+ ## Load This Skill When
16
+
17
+ - Project is approaching launch (Session 5+)
18
+ - User mentions SEO, search rankings, or discoverability
19
+ - Building a marketing site, blog, or e-commerce store
20
+
21
+ ---
22
+
23
+ ## The Basics (Session 5-6)
24
+
25
+ ### Every Page Needs
26
+
27
+ ```typescript
28
+ // Next.js metadata
29
+ export const metadata: Metadata = {
30
+ title: 'Page Title — App Name', // Under 60 characters
31
+ description: 'What this page offers.', // Under 160 characters
32
+ };
33
+ ```
34
+
35
+ ### Title Rules
36
+
37
+ ```
38
+ Homepage: "App Name — One Line Value Prop"
39
+ Inner pages: "Page Topic — App Name"
40
+ Blog posts: "Post Title — App Name"
41
+ ```
42
+
43
+ - Keep under 60 characters
44
+ - Put the important words first
45
+ - Don't stuff keywords
46
+
47
+ ### Description Rules
48
+
49
+ - Write for humans, not search engines
50
+ - Include a call to action ("Learn how to...")
51
+ - Unique per page (never duplicate)
52
+ - 120-160 characters
53
+
54
+ ---
55
+
56
+ ## Open Graph (Social Sharing)
57
+
58
+ ### Every Page Needs
59
+
60
+ ```typescript
61
+ export const metadata: Metadata = {
62
+ openGraph: {
63
+ title: 'Page Title',
64
+ description: 'What this page offers.',
65
+ url: 'https://yourdomain.com/page',
66
+ siteName: 'App Name',
67
+ images: [{
68
+ url: 'https://yourdomain.com/og-image.png',
69
+ width: 1200,
70
+ height: 630,
71
+ }],
72
+ type: 'website',
73
+ },
74
+ twitter: {
75
+ card: 'summary_large_image',
76
+ title: 'Page Title',
77
+ description: 'What this page offers.',
78
+ images: ['https://yourdomain.com/og-image.png'],
79
+ },
80
+ };
81
+ ```
82
+
83
+ ### OG Image Rules
84
+
85
+ - Size: 1200x630px
86
+ - Include app name/logo
87
+ - Readable text (not too small)
88
+ - Create a default OG image for the site
89
+ - Create unique OG images for key pages (homepage, pricing, blog posts)
90
+
91
+ ---
92
+
93
+ ## Sitemap (Session 5)
94
+
95
+ ### Next.js Auto-Generation
96
+
97
+ ```typescript
98
+ // app/sitemap.ts
99
+ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
100
+ const pages = [
101
+ { url: 'https://yourdomain.com', lastModified: new Date() },
102
+ { url: 'https://yourdomain.com/pricing', lastModified: new Date() },
103
+ { url: 'https://yourdomain.com/about', lastModified: new Date() },
104
+ ];
105
+
106
+ // Add dynamic pages
107
+ const posts = await getBlogPosts();
108
+ const postPages = posts.map(post => ({
109
+ url: `https://yourdomain.com/blog/${post.slug}`,
110
+ lastModified: post.updatedAt,
111
+ }));
112
+
113
+ return [...pages, ...postPages];
114
+ }
115
+ ```
116
+
117
+ ### robots.txt
118
+
119
+ ```typescript
120
+ // app/robots.ts
121
+ export default function robots(): MetadataRoute.Robots {
122
+ return {
123
+ rules: { userAgent: '*', allow: '/' },
124
+ sitemap: 'https://yourdomain.com/sitemap.xml',
125
+ };
126
+ }
127
+ ```
128
+
129
+ ---
130
+
131
+ ## Structured Data (Session 6+)
132
+
133
+ ### When to Add
134
+
135
+ - Product pages (e-commerce)
136
+ - Blog posts/articles
137
+ - FAQ pages
138
+ - Organization info
139
+
140
+ ### JSON-LD Pattern
141
+
142
+ ```typescript
143
+ // Component for structured data
144
+ function JsonLd({ data }: { data: Record<string, unknown> }) {
145
+ return (
146
+ <script
147
+ type="application/ld+json"
148
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }}
149
+ />
150
+ );
151
+ }
152
+
153
+ // Usage in a blog post
154
+ <JsonLd data={{
155
+ '@context': 'https://schema.org',
156
+ '@type': 'Article',
157
+ headline: post.title,
158
+ datePublished: post.publishedAt,
159
+ author: { '@type': 'Person', name: 'Author Name' },
160
+ }} />
161
+ ```
162
+
163
+ ### Don't Over-Do It
164
+
165
+ Only add structured data for:
166
+ - Content types Google explicitly supports
167
+ - Pages you want enhanced search results for
168
+ - Don't add it to every page "just in case"
169
+
170
+ ---
171
+
172
+ ## Performance as SEO
173
+
174
+ ### Core Web Vitals
175
+
176
+ Google uses these as ranking signals:
177
+
178
+ | Metric | Target | What It Measures |
179
+ |--------|--------|-----------------|
180
+ | **LCP** (Largest Contentful Paint) | < 2.5s | How fast the main content loads |
181
+ | **INP** (Interaction to Next Paint) | < 200ms | How fast the page responds to clicks |
182
+ | **CLS** (Cumulative Layout Shift) | < 0.1 | How much the page shifts during load |
183
+
184
+ ### Quick Wins
185
+
186
+ 1. **Use Next.js Image** — auto-optimization, lazy loading, sizing
187
+ 2. **Minimize client-side JavaScript** — use Server Components
188
+ 3. **Don't load fonts from Google Fonts CDN** — use `next/font`
189
+ 4. **Preload critical assets** — hero images, above-fold content
190
+ 5. **Avoid layout shifts** — set width/height on images, use skeleton loaders
191
+
192
+ ---
193
+
194
+ ## SEO Checklist (Pre-Launch)
195
+
196
+ - [ ] Every page has unique title and description
197
+ - [ ] OG images set for key pages
198
+ - [ ] Sitemap generated and accessible
199
+ - [ ] robots.txt allows indexing
200
+ - [ ] Core Web Vitals passing (check with PageSpeed Insights)
201
+ - [ ] No broken links (404s)
202
+ - [ ] HTTPS enforced
203
+ - [ ] Mobile-friendly (test at 375px)
204
+ - [ ] Structured data on key pages (if applicable)
205
+
206
+ ---
207
+
208
+ ## What NOT to Do
209
+
210
+ - Don't keyword stuff (Google penalizes this)
211
+ - Don't hide text (white text on white background)
212
+ - Don't create pages just for SEO (thin content)
213
+ - Don't buy backlinks
214
+ - Don't obsess over rankings — focus on content quality
215
+ - Don't add SEO before the product works
216
+
217
+ ---
218
+
219
+ *Last updated: Baton Protocol v3.1*
@@ -0,0 +1,281 @@
1
+ ---
2
+ name: prisma
3
+ description: >-
4
+ Prisma ORM patterns — schema design, migrations, type-safe queries,
5
+ relations, and seeding. Load at Session 1-2 when using Prisma as the
6
+ database ORM. Use when designing schemas, writing queries, or running
7
+ migrations.
8
+ ---
9
+
10
+ # Prisma Skill
11
+
12
+ > Schema-first development. Define your data model, Prisma handles the rest.
13
+
14
+ ---
15
+
16
+ ## Setup (Session 1)
17
+
18
+ ```bash
19
+ npm install prisma @prisma/client
20
+ npx prisma init
21
+ ```
22
+
23
+ This creates:
24
+ - `prisma/schema.prisma` — your data model
25
+ - `.env` — database connection string
26
+
27
+ ### Database URL
28
+
29
+ ```bash
30
+ # .env
31
+ DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
32
+ ```
33
+
34
+ ---
35
+
36
+ ## Schema Design
37
+
38
+ ### Basic Model
39
+
40
+ ```prisma
41
+ model User {
42
+ id String @id @default(cuid())
43
+ email String @unique
44
+ name String?
45
+ createdAt DateTime @default(now())
46
+ updatedAt DateTime @updatedAt
47
+
48
+ posts Post[]
49
+ }
50
+
51
+ model Post {
52
+ id String @id @default(cuid())
53
+ title String
54
+ content String?
55
+ published Boolean @default(false)
56
+ createdAt DateTime @default(now())
57
+ updatedAt DateTime @updatedAt
58
+
59
+ author User @relation(fields: [authorId], references: [id])
60
+ authorId String
61
+ }
62
+ ```
63
+
64
+ ### Naming Conventions
65
+
66
+ | Thing | Convention | Example |
67
+ |-------|-----------|---------|
68
+ | Models | PascalCase, singular | `User`, `OrderItem` |
69
+ | Fields | camelCase | `firstName`, `createdAt` |
70
+ | Relations | camelCase, descriptive | `author`, `orderItems` |
71
+ | Enums | PascalCase | `Role`, `OrderStatus` |
72
+
73
+ ### Enums
74
+
75
+ ```prisma
76
+ enum Role {
77
+ USER
78
+ ADMIN
79
+ }
80
+
81
+ enum OrderStatus {
82
+ PENDING
83
+ CONFIRMED
84
+ SHIPPED
85
+ DELIVERED
86
+ CANCELLED
87
+ }
88
+ ```
89
+
90
+ ---
91
+
92
+ ## Migrations
93
+
94
+ ### Create a Migration
95
+
96
+ ```bash
97
+ npx prisma migrate dev --name add_users_table
98
+ ```
99
+
100
+ This:
101
+ 1. Generates SQL migration file
102
+ 2. Applies it to your dev database
103
+ 3. Regenerates Prisma Client types
104
+
105
+ ### Migration Workflow
106
+
107
+ ```
108
+ 1. Edit schema.prisma
109
+ 2. Run prisma migrate dev
110
+ 3. Check generated SQL
111
+ 4. Commit migration file
112
+ 5. Build features
113
+ ```
114
+
115
+ ### Production Migrations
116
+
117
+ ```bash
118
+ npx prisma migrate deploy
119
+ ```
120
+
121
+ **Rule:** Never run `migrate dev` in production. Always use `migrate deploy`.
122
+
123
+ ---
124
+
125
+ ## Prisma Client (Queries)
126
+
127
+ ### Setup
128
+
129
+ ```typescript
130
+ // lib/db.ts
131
+ import { PrismaClient } from '@prisma/client';
132
+
133
+ const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
134
+
135
+ export const prisma = globalForPrisma.prisma || new PrismaClient();
136
+
137
+ if (process.env.NODE_ENV !== 'production') {
138
+ globalForPrisma.prisma = prisma;
139
+ }
140
+ ```
141
+
142
+ **Why the global pattern:** Prevents creating multiple Prisma Client instances during hot reload in development.
143
+
144
+ ### Common Queries
145
+
146
+ ```typescript
147
+ // Find many with filter
148
+ const posts = await prisma.post.findMany({
149
+ where: { published: true, authorId: userId },
150
+ orderBy: { createdAt: 'desc' },
151
+ take: 20,
152
+ });
153
+
154
+ // Find one
155
+ const user = await prisma.user.findUnique({
156
+ where: { id: userId },
157
+ });
158
+
159
+ // Find one or throw
160
+ const user = await prisma.user.findUniqueOrThrow({
161
+ where: { id: userId },
162
+ });
163
+
164
+ // Create
165
+ const post = await prisma.post.create({
166
+ data: {
167
+ title: 'Hello',
168
+ authorId: userId,
169
+ },
170
+ });
171
+
172
+ // Update
173
+ const post = await prisma.post.update({
174
+ where: { id: postId },
175
+ data: { published: true },
176
+ });
177
+
178
+ // Delete
179
+ await prisma.post.delete({
180
+ where: { id: postId },
181
+ });
182
+ ```
183
+
184
+ ### Include Relations
185
+
186
+ ```typescript
187
+ // Get user with their posts
188
+ const user = await prisma.user.findUnique({
189
+ where: { id: userId },
190
+ include: { posts: true },
191
+ });
192
+
193
+ // Selective include
194
+ const user = await prisma.user.findUnique({
195
+ where: { id: userId },
196
+ include: {
197
+ posts: {
198
+ where: { published: true },
199
+ orderBy: { createdAt: 'desc' },
200
+ take: 5,
201
+ },
202
+ },
203
+ });
204
+ ```
205
+
206
+ ### Select Specific Fields
207
+
208
+ ```typescript
209
+ // Only get what you need
210
+ const users = await prisma.user.findMany({
211
+ select: {
212
+ id: true,
213
+ name: true,
214
+ email: true,
215
+ },
216
+ });
217
+ ```
218
+
219
+ ---
220
+
221
+ ## Transactions
222
+
223
+ ```typescript
224
+ // When multiple operations must succeed together
225
+ const [order, updatedInventory] = await prisma.$transaction([
226
+ prisma.order.create({ data: orderData }),
227
+ prisma.product.update({
228
+ where: { id: productId },
229
+ data: { inventory: { decrement: quantity } },
230
+ }),
231
+ ]);
232
+ ```
233
+
234
+ ---
235
+
236
+ ## Seeding
237
+
238
+ ```typescript
239
+ // prisma/seed.ts
240
+ import { prisma } from '../lib/db';
241
+
242
+ async function main() {
243
+ await prisma.user.create({
244
+ data: {
245
+ email: 'test@example.com',
246
+ name: 'Test User',
247
+ posts: {
248
+ create: [
249
+ { title: 'First Post', published: true },
250
+ { title: 'Draft Post' },
251
+ ],
252
+ },
253
+ },
254
+ });
255
+ }
256
+
257
+ main()
258
+ .catch(console.error)
259
+ .finally(() => prisma.$disconnect());
260
+ ```
261
+
262
+ ```bash
263
+ npx prisma db seed
264
+ ```
265
+
266
+ ---
267
+
268
+ ## Common Mistakes
269
+
270
+ | Mistake | Fix |
271
+ |---------|-----|
272
+ | Not using the global pattern | Multiple clients = connection pool issues |
273
+ | Fetching all fields when you need 2 | Use `select` |
274
+ | N+1 queries (looping with queries) | Use `include` or `select` with relations |
275
+ | Running `migrate dev` in production | Use `migrate deploy` |
276
+ | Not committing migration files | Always commit `prisma/migrations/` |
277
+ | Editing generated migration SQL | Create a new migration instead |
278
+
279
+ ---
280
+
281
+ *Last updated: Baton Protocol v3.1*