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.
- package/package.json +1 -1
- package/src/constants.js +3 -3
- package/templates/BATON_v3.1.md +53 -15
- package/templates/skills/domains/api/SKILL.md +318 -0
- package/templates/skills/domains/ecommerce/SKILL.md +277 -0
- package/templates/skills/domains/portfolio/SKILL.md +213 -0
- package/templates/skills/domains/saas/SKILL.md +252 -0
- package/templates/skills/patterns/authentication/SKILL.md +245 -0
- package/templates/skills/patterns/database-design/SKILL.md +230 -0
- package/templates/skills/patterns/email/SKILL.md +236 -0
- package/templates/skills/patterns/file-uploads/SKILL.md +216 -0
- package/templates/skills/patterns/payments/SKILL.md +246 -0
- package/templates/skills/patterns/seo/SKILL.md +219 -0
- package/templates/skills/stacks/prisma/SKILL.md +281 -0
- package/templates/skills/stacks/shadcn/SKILL.md +270 -0
- package/templates/skills/stacks/tailwind/SKILL.md +242 -0
- package/templates/skills/stacks/typescript/SKILL.md +241 -0
- package/templates/skills/stacks/vercel/SKILL.md +232 -0
|
@@ -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*
|