@girardmedia/bootspring 1.2.0 → 2.0.3
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/README.md +107 -14
- package/bin/bootspring.js +166 -27
- package/cli/agent.js +189 -17
- package/cli/analyze.js +499 -0
- package/cli/audit.js +557 -0
- package/cli/auth.js +495 -38
- package/cli/billing.js +302 -0
- package/cli/build.js +695 -0
- package/cli/business.js +109 -26
- package/cli/checkpoint-utils.js +168 -0
- package/cli/checkpoint.js +639 -0
- package/cli/cloud-sync.js +447 -0
- package/cli/content.js +198 -0
- package/cli/context.js +1 -1
- package/cli/deploy.js +543 -0
- package/cli/fundraise.js +112 -50
- package/cli/github-cmd.js +435 -0
- package/cli/health.js +477 -0
- package/cli/init.js +84 -13
- package/cli/legal.js +107 -95
- package/cli/log.js +2 -2
- package/cli/loop.js +976 -73
- package/cli/manager.js +711 -0
- package/cli/metrics.js +480 -0
- package/cli/monitor.js +812 -0
- package/cli/onboard.js +521 -0
- package/cli/orchestrator.js +12 -24
- package/cli/prd.js +594 -0
- package/cli/preseed-start.js +1483 -0
- package/cli/preseed.js +2302 -0
- package/cli/project.js +436 -0
- package/cli/quality.js +233 -0
- package/cli/security.js +913 -0
- package/cli/seed.js +1441 -5
- package/cli/skill.js +273 -211
- package/cli/suggest.js +989 -0
- package/cli/switch.js +453 -0
- package/cli/visualize.js +527 -0
- package/cli/watch.js +769 -0
- package/cli/workspace.js +607 -0
- package/core/analyze-workflow.js +1134 -0
- package/core/api-client.js +535 -22
- package/core/audit-workflow.js +1350 -0
- package/core/build-orchestrator.js +480 -0
- package/core/build-state.js +577 -0
- package/core/checkpoint-engine.js +408 -0
- package/core/config.js +1109 -26
- package/core/context-loader.js +21 -1
- package/core/deploy-workflow.js +836 -0
- package/core/entitlements.js +93 -22
- package/core/github-sync.js +610 -0
- package/core/index.js +8 -1
- package/core/ingest.js +1111 -0
- package/core/metrics-engine.js +768 -0
- package/core/onboard-workflow.js +1007 -0
- package/core/preseed-workflow.js +934 -0
- package/core/preseed.js +1617 -0
- package/core/project-context.js +325 -0
- package/core/project-state.js +694 -0
- package/core/r2-sync.js +583 -0
- package/core/scaffold.js +525 -7
- package/core/session.js +258 -0
- package/core/task-extractor.js +758 -0
- package/core/telemetry.js +28 -6
- package/core/tier-enforcement.js +737 -0
- package/core/utils.js +38 -14
- package/generators/questionnaire.js +15 -12
- package/generators/sections/ai.js +7 -7
- package/generators/sections/content.js +300 -0
- package/generators/sections/index.js +3 -0
- package/generators/sections/plugins.js +7 -6
- package/generators/templates/build-planning.template.js +596 -0
- package/generators/templates/content.template.js +819 -0
- package/generators/templates/index.js +2 -1
- package/hooks/git-autopilot.js +1250 -0
- package/hooks/index.js +9 -0
- package/intelligence/agent-collab.js +2057 -0
- package/intelligence/auto-suggest.js +634 -0
- package/intelligence/content-gen.js +1589 -0
- package/intelligence/cross-project.js +1647 -0
- package/intelligence/index.js +184 -0
- package/intelligence/learning/insights.json +517 -7
- package/intelligence/learning/pattern-learner.js +1008 -14
- package/intelligence/memory/decision-tracker.js +1431 -31
- package/intelligence/memory/decisions.jsonl +0 -0
- package/intelligence/orchestrator.js +2896 -1
- package/intelligence/prd.js +92 -1
- package/intelligence/recommendation-weights.json +14 -2
- package/intelligence/recommendations.js +463 -9
- package/intelligence/workflow-composer.js +1451 -0
- package/marketplace/index.d.ts +324 -0
- package/marketplace/index.js +1921 -0
- package/mcp/contracts/mcp-contract.v1.json +342 -4
- package/mcp/registry.js +680 -3
- package/mcp/response-formatter.js +23 -0
- package/mcp/tools/assist-tool.js +78 -4
- package/mcp/tools/autopilot-tool.js +408 -0
- package/mcp/tools/content-tool.js +571 -0
- package/mcp/tools/dashboard-tool.js +251 -5
- package/mcp/tools/mvp-tool.js +344 -0
- package/mcp/tools/plugin-tool.js +23 -1
- package/mcp/tools/prd-tool.js +579 -0
- package/mcp/tools/seed-tool.js +447 -0
- package/mcp/tools/skill-tool.js +43 -14
- package/mcp/tools/suggest-tool.js +147 -0
- package/package.json +15 -6
- package/agents/README.md +0 -93
- package/agents/ai-integration-expert/context.md +0 -386
- package/agents/api-expert/context.md +0 -416
- package/agents/architecture-expert/context.md +0 -454
- package/agents/auth-expert/context.md +0 -399
- package/agents/backend-expert/context.md +0 -483
- package/agents/business-strategy-expert/context.md +0 -180
- package/agents/code-review-expert/context.md +0 -365
- package/agents/competitive-analysis-expert/context.md +0 -239
- package/agents/data-modeling-expert/context.md +0 -352
- package/agents/database-expert/context.md +0 -250
- package/agents/devops-expert/context.md +0 -446
- package/agents/email-expert/context.md +0 -379
- package/agents/financial-expert/context.md +0 -213
- package/agents/frontend-expert/context.md +0 -364
- package/agents/fundraising-expert/context.md +0 -257
- package/agents/growth-expert/context.md +0 -249
- package/agents/index.js +0 -140
- package/agents/investor-relations-expert/context.md +0 -266
- package/agents/legal-expert/context.md +0 -284
- package/agents/marketing-expert/context.md +0 -236
- package/agents/monitoring-expert/context.md +0 -362
- package/agents/operations-expert/context.md +0 -279
- package/agents/partnerships-expert/context.md +0 -286
- package/agents/payment-expert/context.md +0 -340
- package/agents/performance-expert/context.md +0 -377
- package/agents/private-equity-expert/context.md +0 -246
- package/agents/railway-expert/context.md +0 -284
- package/agents/research-expert/context.md +0 -245
- package/agents/sales-expert/context.md +0 -241
- package/agents/security-expert/context.md +0 -343
- package/agents/testing-expert/context.md +0 -414
- package/agents/ui-ux-expert/context.md +0 -448
- package/agents/vercel-expert/context.md +0 -426
- package/skills/index.js +0 -787
- package/skills/patterns/README.md +0 -163
- package/skills/patterns/ai/agents.md +0 -281
- package/skills/patterns/ai/claude.md +0 -138
- package/skills/patterns/ai/embeddings.md +0 -150
- package/skills/patterns/ai/rag.md +0 -266
- package/skills/patterns/ai/streaming.md +0 -170
- package/skills/patterns/ai/structured-output.md +0 -162
- package/skills/patterns/ai/tools.md +0 -154
- package/skills/patterns/analytics/tracking.md +0 -220
- package/skills/patterns/api/errors.md +0 -296
- package/skills/patterns/api/graphql.md +0 -440
- package/skills/patterns/api/middleware.md +0 -279
- package/skills/patterns/api/openapi.md +0 -285
- package/skills/patterns/api/rate-limiting.md +0 -231
- package/skills/patterns/api/route-handler.md +0 -217
- package/skills/patterns/api/server-action.md +0 -249
- package/skills/patterns/api/versioning.md +0 -443
- package/skills/patterns/api/webhooks.md +0 -247
- package/skills/patterns/auth/clerk.md +0 -132
- package/skills/patterns/auth/mfa.md +0 -313
- package/skills/patterns/auth/nextauth.md +0 -140
- package/skills/patterns/auth/oauth.md +0 -237
- package/skills/patterns/auth/rbac.md +0 -152
- package/skills/patterns/auth/session-management.md +0 -367
- package/skills/patterns/auth/session.md +0 -120
- package/skills/patterns/database/audit.md +0 -177
- package/skills/patterns/database/migrations.md +0 -177
- package/skills/patterns/database/pagination.md +0 -230
- package/skills/patterns/database/pooling.md +0 -357
- package/skills/patterns/database/prisma.md +0 -180
- package/skills/patterns/database/relations.md +0 -187
- package/skills/patterns/database/seeding.md +0 -246
- package/skills/patterns/database/soft-delete.md +0 -153
- package/skills/patterns/database/transactions.md +0 -162
- package/skills/patterns/deployment/ci-cd.md +0 -231
- package/skills/patterns/deployment/docker.md +0 -188
- package/skills/patterns/deployment/monitoring.md +0 -387
- package/skills/patterns/deployment/vercel.md +0 -160
- package/skills/patterns/email/resend.md +0 -143
- package/skills/patterns/email/templates.md +0 -245
- package/skills/patterns/email/transactional.md +0 -503
- package/skills/patterns/email/verification.md +0 -176
- package/skills/patterns/files/download.md +0 -243
- package/skills/patterns/files/upload.md +0 -239
- package/skills/patterns/i18n/nextintl.md +0 -188
- package/skills/patterns/logging/structured.md +0 -292
- package/skills/patterns/notifications/email-queue.md +0 -248
- package/skills/patterns/notifications/push.md +0 -279
- package/skills/patterns/payments/checkout.md +0 -303
- package/skills/patterns/payments/invoices.md +0 -287
- package/skills/patterns/payments/portal.md +0 -245
- package/skills/patterns/payments/stripe.md +0 -272
- package/skills/patterns/payments/subscriptions.md +0 -300
- package/skills/patterns/payments/usage.md +0 -279
- package/skills/patterns/performance/caching.md +0 -276
- package/skills/patterns/performance/code-splitting.md +0 -233
- package/skills/patterns/performance/edge.md +0 -254
- package/skills/patterns/performance/isr.md +0 -266
- package/skills/patterns/performance/lazy-loading.md +0 -281
- package/skills/patterns/realtime/sse.md +0 -327
- package/skills/patterns/realtime/websockets.md +0 -336
- package/skills/patterns/search/filtering.md +0 -329
- package/skills/patterns/search/fulltext.md +0 -260
- package/skills/patterns/security/audit-logging.md +0 -444
- package/skills/patterns/security/csrf.md +0 -234
- package/skills/patterns/security/headers.md +0 -252
- package/skills/patterns/security/sanitization.md +0 -258
- package/skills/patterns/security/secrets.md +0 -261
- package/skills/patterns/security/validation.md +0 -268
- package/skills/patterns/security/xss.md +0 -229
- package/skills/patterns/seo/metadata.md +0 -252
- package/skills/patterns/state/context.md +0 -349
- package/skills/patterns/state/react-query.md +0 -313
- package/skills/patterns/state/url-state.md +0 -482
- package/skills/patterns/state/zustand.md +0 -262
- package/skills/patterns/testing/api.md +0 -259
- package/skills/patterns/testing/component.md +0 -233
- package/skills/patterns/testing/coverage.md +0 -207
- package/skills/patterns/testing/fixtures.md +0 -225
- package/skills/patterns/testing/integration.md +0 -436
- package/skills/patterns/testing/mocking.md +0 -177
- package/skills/patterns/testing/playwright.md +0 -162
- package/skills/patterns/testing/snapshot.md +0 -175
- package/skills/patterns/testing/vitest.md +0 -307
- package/skills/patterns/ui/accordions.md +0 -395
- package/skills/patterns/ui/cards.md +0 -299
- package/skills/patterns/ui/dropdowns.md +0 -476
- package/skills/patterns/ui/empty-states.md +0 -320
- package/skills/patterns/ui/forms.md +0 -405
- package/skills/patterns/ui/inputs.md +0 -319
- package/skills/patterns/ui/layouts.md +0 -282
- package/skills/patterns/ui/loading.md +0 -291
- package/skills/patterns/ui/modals.md +0 -338
- package/skills/patterns/ui/navigation.md +0 -374
- package/skills/patterns/ui/tables.md +0 -407
- package/skills/patterns/ui/toasts.md +0 -300
- package/skills/patterns/ui/tooltips.md +0 -396
- package/skills/patterns/utils/dates.md +0 -435
- package/skills/patterns/utils/errors.md +0 -451
- package/skills/patterns/utils/formatting.md +0 -345
- package/skills/patterns/utils/validation.md +0 -434
- package/templates/bootspring.config.js +0 -83
- package/templates/business/business-model-canvas.md +0 -246
- package/templates/business/business-plan.md +0 -266
- package/templates/business/competitive-analysis.md +0 -312
- package/templates/fundraising/data-room-checklist.md +0 -300
- package/templates/fundraising/investor-research.md +0 -243
- package/templates/fundraising/pitch-deck-outline.md +0 -253
- package/templates/legal/gdpr-checklist.md +0 -339
- package/templates/legal/privacy-policy.md +0 -285
- package/templates/legal/terms-of-service.md +0 -222
- package/templates/mcp.json +0 -9
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
# XSS Prevention Patterns
|
|
2
|
-
|
|
3
|
-
Patterns for preventing Cross-Site Scripting attacks.
|
|
4
|
-
|
|
5
|
-
## HTML Sanitization
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
// lib/sanitize.ts
|
|
9
|
-
import DOMPurify from 'isomorphic-dompurify'
|
|
10
|
-
|
|
11
|
-
// Basic sanitization - remove all HTML
|
|
12
|
-
export function stripHtml(input: string): string {
|
|
13
|
-
return DOMPurify.sanitize(input, { ALLOWED_TAGS: [] })
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// Allow safe HTML (for rich text)
|
|
17
|
-
export function sanitizeHtml(input: string): string {
|
|
18
|
-
return DOMPurify.sanitize(input, {
|
|
19
|
-
ALLOWED_TAGS: [
|
|
20
|
-
'b', 'i', 'em', 'strong', 'a', 'p', 'br',
|
|
21
|
-
'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4',
|
|
22
|
-
'blockquote', 'code', 'pre'
|
|
23
|
-
],
|
|
24
|
-
ALLOWED_ATTR: ['href', 'target', 'rel'],
|
|
25
|
-
ALLOW_DATA_ATTR: false
|
|
26
|
-
})
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Sanitize with custom hooks
|
|
30
|
-
export function sanitizeRichContent(input: string): string {
|
|
31
|
-
return DOMPurify.sanitize(input, {
|
|
32
|
-
ADD_TAGS: ['iframe'],
|
|
33
|
-
ADD_ATTR: ['allow', 'allowfullscreen', 'frameborder', 'scrolling'],
|
|
34
|
-
ALLOWED_URI_REGEXP: /^(?:(?:https?|mailto):|[^a-z]|[a-z+.-]+(?:[^a-z+.-:]|$))/i,
|
|
35
|
-
// Only allow specific iframe sources
|
|
36
|
-
CUSTOM_ELEMENT_HANDLING: {
|
|
37
|
-
tagNameCheck: (tagName) => tagName === 'iframe',
|
|
38
|
-
attributeNameCheck: () => true,
|
|
39
|
-
allowCustomizedBuiltInElements: false
|
|
40
|
-
}
|
|
41
|
-
})
|
|
42
|
-
}
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
## Escape Functions
|
|
46
|
-
|
|
47
|
-
```typescript
|
|
48
|
-
// lib/escape.ts
|
|
49
|
-
|
|
50
|
-
// Escape HTML entities
|
|
51
|
-
export function escapeHtml(str: string): string {
|
|
52
|
-
const htmlEscapes: Record<string, string> = {
|
|
53
|
-
'&': '&',
|
|
54
|
-
'<': '<',
|
|
55
|
-
'>': '>',
|
|
56
|
-
'"': '"',
|
|
57
|
-
"'": ''',
|
|
58
|
-
'/': '/'
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return str.replace(/[&<>"'/]/g, char => htmlEscapes[char])
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Escape for use in JavaScript strings
|
|
65
|
-
export function escapeJs(str: string): string {
|
|
66
|
-
return str
|
|
67
|
-
.replace(/\\/g, '\\\\')
|
|
68
|
-
.replace(/'/g, "\\'")
|
|
69
|
-
.replace(/"/g, '\\"')
|
|
70
|
-
.replace(/\n/g, '\\n')
|
|
71
|
-
.replace(/\r/g, '\\r')
|
|
72
|
-
.replace(/\t/g, '\\t')
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Escape for URLs
|
|
76
|
-
export function escapeUrl(str: string): string {
|
|
77
|
-
return encodeURIComponent(str)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Escape for CSS
|
|
81
|
-
export function escapeCss(str: string): string {
|
|
82
|
-
return str.replace(/[^a-zA-Z0-9]/g, char => `\\${char.charCodeAt(0).toString(16)} `)
|
|
83
|
-
}
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
## Content Security Policy
|
|
87
|
-
|
|
88
|
-
```typescript
|
|
89
|
-
// middleware.ts
|
|
90
|
-
import { NextResponse } from 'next/server'
|
|
91
|
-
import type { NextRequest } from 'next/server'
|
|
92
|
-
|
|
93
|
-
export function middleware(request: NextRequest) {
|
|
94
|
-
const response = NextResponse.next()
|
|
95
|
-
|
|
96
|
-
// Content Security Policy
|
|
97
|
-
const csp = [
|
|
98
|
-
"default-src 'self'",
|
|
99
|
-
"script-src 'self' 'unsafe-inline' 'unsafe-eval'", // Adjust for your needs
|
|
100
|
-
"style-src 'self' 'unsafe-inline'",
|
|
101
|
-
"img-src 'self' data: https:",
|
|
102
|
-
"font-src 'self'",
|
|
103
|
-
"connect-src 'self' https://api.stripe.com",
|
|
104
|
-
"frame-ancestors 'none'",
|
|
105
|
-
"base-uri 'self'",
|
|
106
|
-
"form-action 'self'"
|
|
107
|
-
].join('; ')
|
|
108
|
-
|
|
109
|
-
response.headers.set('Content-Security-Policy', csp)
|
|
110
|
-
response.headers.set('X-Content-Type-Options', 'nosniff')
|
|
111
|
-
response.headers.set('X-Frame-Options', 'DENY')
|
|
112
|
-
response.headers.set('X-XSS-Protection', '1; mode=block')
|
|
113
|
-
|
|
114
|
-
return response
|
|
115
|
-
}
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
## Safe React Rendering
|
|
119
|
-
|
|
120
|
-
```tsx
|
|
121
|
-
// components/SafeHtml.tsx
|
|
122
|
-
import DOMPurify from 'isomorphic-dompurify'
|
|
123
|
-
|
|
124
|
-
interface Props {
|
|
125
|
-
html: string
|
|
126
|
-
className?: string
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export function SafeHtml({ html, className }: Props) {
|
|
130
|
-
const sanitized = DOMPurify.sanitize(html)
|
|
131
|
-
|
|
132
|
-
return (
|
|
133
|
-
<div
|
|
134
|
-
className={className}
|
|
135
|
-
dangerouslySetInnerHTML={{ __html: sanitized }}
|
|
136
|
-
/>
|
|
137
|
-
)
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Usage
|
|
141
|
-
<SafeHtml html={userContent} />
|
|
142
|
-
|
|
143
|
-
// DON'T do this:
|
|
144
|
-
// <div dangerouslySetInnerHTML={{ __html: userContent }} />
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
## URL Validation
|
|
148
|
-
|
|
149
|
-
```typescript
|
|
150
|
-
// lib/url.ts
|
|
151
|
-
const ALLOWED_PROTOCOLS = ['http:', 'https:', 'mailto:']
|
|
152
|
-
|
|
153
|
-
export function isValidUrl(url: string): boolean {
|
|
154
|
-
try {
|
|
155
|
-
const parsed = new URL(url)
|
|
156
|
-
return ALLOWED_PROTOCOLS.includes(parsed.protocol)
|
|
157
|
-
} catch {
|
|
158
|
-
return false
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export function sanitizeUrl(url: string): string {
|
|
163
|
-
if (isValidUrl(url)) {
|
|
164
|
-
return url
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Return safe fallback
|
|
168
|
-
return '#'
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Usage in React
|
|
172
|
-
function SafeLink({ href, children }: { href: string; children: React.ReactNode }) {
|
|
173
|
-
const safeHref = sanitizeUrl(href)
|
|
174
|
-
|
|
175
|
-
return (
|
|
176
|
-
<a href={safeHref} rel="noopener noreferrer">
|
|
177
|
-
{children}
|
|
178
|
-
</a>
|
|
179
|
-
)
|
|
180
|
-
}
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
## Input Validation
|
|
184
|
-
|
|
185
|
-
```typescript
|
|
186
|
-
// lib/validation.ts
|
|
187
|
-
import { z } from 'zod'
|
|
188
|
-
|
|
189
|
-
// Text input - strip HTML by default
|
|
190
|
-
export const safeTextSchema = z.string().transform(stripHtml)
|
|
191
|
-
|
|
192
|
-
// Rich text - sanitize HTML
|
|
193
|
-
export const richTextSchema = z.string().transform(sanitizeHtml)
|
|
194
|
-
|
|
195
|
-
// URL - validate protocol
|
|
196
|
-
export const safeUrlSchema = z.string().refine(isValidUrl, {
|
|
197
|
-
message: 'Invalid URL'
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
// Example schema
|
|
201
|
-
const PostSchema = z.object({
|
|
202
|
-
title: safeTextSchema.min(1).max(200),
|
|
203
|
-
content: richTextSchema,
|
|
204
|
-
externalLink: safeUrlSchema.optional()
|
|
205
|
-
})
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
## JSON Response Headers
|
|
209
|
-
|
|
210
|
-
```typescript
|
|
211
|
-
// app/api/data/route.ts
|
|
212
|
-
export async function GET() {
|
|
213
|
-
const data = await getData()
|
|
214
|
-
|
|
215
|
-
return Response.json(data, {
|
|
216
|
-
headers: {
|
|
217
|
-
'Content-Type': 'application/json',
|
|
218
|
-
'X-Content-Type-Options': 'nosniff'
|
|
219
|
-
}
|
|
220
|
-
})
|
|
221
|
-
}
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
## When to Use
|
|
225
|
-
|
|
226
|
-
- User-generated content
|
|
227
|
-
- Rich text editors
|
|
228
|
-
- External links
|
|
229
|
-
- API responses
|
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
# SEO Metadata Patterns
|
|
2
|
-
|
|
3
|
-
Patterns for SEO optimization in Next.js.
|
|
4
|
-
|
|
5
|
-
## Static Metadata
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
// app/layout.tsx
|
|
9
|
-
import type { Metadata } from 'next'
|
|
10
|
-
|
|
11
|
-
export const metadata: Metadata = {
|
|
12
|
-
title: {
|
|
13
|
-
default: 'My App',
|
|
14
|
-
template: '%s | My App'
|
|
15
|
-
},
|
|
16
|
-
description: 'The best app for productivity',
|
|
17
|
-
keywords: ['productivity', 'tasks', 'collaboration'],
|
|
18
|
-
authors: [{ name: 'Company Name' }],
|
|
19
|
-
creator: 'Company Name',
|
|
20
|
-
publisher: 'Company Name',
|
|
21
|
-
metadataBase: new URL('https://myapp.com'),
|
|
22
|
-
openGraph: {
|
|
23
|
-
type: 'website',
|
|
24
|
-
locale: 'en_US',
|
|
25
|
-
url: 'https://myapp.com',
|
|
26
|
-
siteName: 'My App',
|
|
27
|
-
title: 'My App - Productivity Made Simple',
|
|
28
|
-
description: 'The best app for productivity',
|
|
29
|
-
images: [
|
|
30
|
-
{
|
|
31
|
-
url: '/og-image.png',
|
|
32
|
-
width: 1200,
|
|
33
|
-
height: 630,
|
|
34
|
-
alt: 'My App'
|
|
35
|
-
}
|
|
36
|
-
]
|
|
37
|
-
},
|
|
38
|
-
twitter: {
|
|
39
|
-
card: 'summary_large_image',
|
|
40
|
-
title: 'My App',
|
|
41
|
-
description: 'The best app for productivity',
|
|
42
|
-
images: ['/twitter-image.png'],
|
|
43
|
-
creator: '@myapp'
|
|
44
|
-
},
|
|
45
|
-
robots: {
|
|
46
|
-
index: true,
|
|
47
|
-
follow: true,
|
|
48
|
-
googleBot: {
|
|
49
|
-
index: true,
|
|
50
|
-
follow: true,
|
|
51
|
-
'max-video-preview': -1,
|
|
52
|
-
'max-image-preview': 'large',
|
|
53
|
-
'max-snippet': -1
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
verification: {
|
|
57
|
-
google: 'google-site-verification-code',
|
|
58
|
-
yandex: 'yandex-verification-code'
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
## Dynamic Metadata
|
|
64
|
-
|
|
65
|
-
```typescript
|
|
66
|
-
// app/blog/[slug]/page.tsx
|
|
67
|
-
import type { Metadata } from 'next'
|
|
68
|
-
|
|
69
|
-
interface Props {
|
|
70
|
-
params: { slug: string }
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
|
74
|
-
const post = await getPost(params.slug)
|
|
75
|
-
|
|
76
|
-
if (!post) {
|
|
77
|
-
return { title: 'Post Not Found' }
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return {
|
|
81
|
-
title: post.title,
|
|
82
|
-
description: post.excerpt,
|
|
83
|
-
openGraph: {
|
|
84
|
-
title: post.title,
|
|
85
|
-
description: post.excerpt,
|
|
86
|
-
type: 'article',
|
|
87
|
-
publishedTime: post.publishedAt.toISOString(),
|
|
88
|
-
authors: [post.author.name],
|
|
89
|
-
images: [
|
|
90
|
-
{
|
|
91
|
-
url: post.image,
|
|
92
|
-
width: 1200,
|
|
93
|
-
height: 630,
|
|
94
|
-
alt: post.title
|
|
95
|
-
}
|
|
96
|
-
]
|
|
97
|
-
},
|
|
98
|
-
twitter: {
|
|
99
|
-
card: 'summary_large_image',
|
|
100
|
-
title: post.title,
|
|
101
|
-
description: post.excerpt,
|
|
102
|
-
images: [post.image]
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export default async function BlogPost({ params }: Props) {
|
|
108
|
-
const post = await getPost(params.slug)
|
|
109
|
-
return <article>{/* ... */}</article>
|
|
110
|
-
}
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
## JSON-LD Structured Data
|
|
114
|
-
|
|
115
|
-
```tsx
|
|
116
|
-
// components/JsonLd.tsx
|
|
117
|
-
export function JsonLd({ data }: { data: object }) {
|
|
118
|
-
return (
|
|
119
|
-
<script
|
|
120
|
-
type="application/ld+json"
|
|
121
|
-
dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }}
|
|
122
|
-
/>
|
|
123
|
-
)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Article JSON-LD
|
|
127
|
-
export function ArticleJsonLd({ post }: { post: Post }) {
|
|
128
|
-
const data = {
|
|
129
|
-
'@context': 'https://schema.org',
|
|
130
|
-
'@type': 'Article',
|
|
131
|
-
headline: post.title,
|
|
132
|
-
description: post.excerpt,
|
|
133
|
-
image: post.image,
|
|
134
|
-
datePublished: post.publishedAt,
|
|
135
|
-
dateModified: post.updatedAt,
|
|
136
|
-
author: {
|
|
137
|
-
'@type': 'Person',
|
|
138
|
-
name: post.author.name
|
|
139
|
-
},
|
|
140
|
-
publisher: {
|
|
141
|
-
'@type': 'Organization',
|
|
142
|
-
name: 'My App',
|
|
143
|
-
logo: {
|
|
144
|
-
'@type': 'ImageObject',
|
|
145
|
-
url: 'https://myapp.com/logo.png'
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return <JsonLd data={data} />
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Product JSON-LD
|
|
154
|
-
export function ProductJsonLd({ product }: { product: Product }) {
|
|
155
|
-
const data = {
|
|
156
|
-
'@context': 'https://schema.org',
|
|
157
|
-
'@type': 'Product',
|
|
158
|
-
name: product.name,
|
|
159
|
-
description: product.description,
|
|
160
|
-
image: product.images,
|
|
161
|
-
sku: product.sku,
|
|
162
|
-
offers: {
|
|
163
|
-
'@type': 'Offer',
|
|
164
|
-
price: product.price,
|
|
165
|
-
priceCurrency: 'USD',
|
|
166
|
-
availability: product.inStock
|
|
167
|
-
? 'https://schema.org/InStock'
|
|
168
|
-
: 'https://schema.org/OutOfStock'
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return <JsonLd data={data} />
|
|
173
|
-
}
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
## Sitemap
|
|
177
|
-
|
|
178
|
-
```typescript
|
|
179
|
-
// app/sitemap.ts
|
|
180
|
-
import { MetadataRoute } from 'next'
|
|
181
|
-
|
|
182
|
-
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|
183
|
-
const baseUrl = 'https://myapp.com'
|
|
184
|
-
|
|
185
|
-
// Static pages
|
|
186
|
-
const staticPages = [
|
|
187
|
-
{ url: baseUrl, lastModified: new Date(), priority: 1 },
|
|
188
|
-
{ url: `${baseUrl}/about`, lastModified: new Date(), priority: 0.8 },
|
|
189
|
-
{ url: `${baseUrl}/pricing`, lastModified: new Date(), priority: 0.8 }
|
|
190
|
-
]
|
|
191
|
-
|
|
192
|
-
// Dynamic pages
|
|
193
|
-
const posts = await prisma.post.findMany({
|
|
194
|
-
where: { published: true },
|
|
195
|
-
select: { slug: true, updatedAt: true }
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
const postPages = posts.map(post => ({
|
|
199
|
-
url: `${baseUrl}/blog/${post.slug}`,
|
|
200
|
-
lastModified: post.updatedAt,
|
|
201
|
-
priority: 0.6
|
|
202
|
-
}))
|
|
203
|
-
|
|
204
|
-
return [...staticPages, ...postPages]
|
|
205
|
-
}
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
## Robots.txt
|
|
209
|
-
|
|
210
|
-
```typescript
|
|
211
|
-
// app/robots.ts
|
|
212
|
-
import { MetadataRoute } from 'next'
|
|
213
|
-
|
|
214
|
-
export default function robots(): MetadataRoute.Robots {
|
|
215
|
-
return {
|
|
216
|
-
rules: [
|
|
217
|
-
{
|
|
218
|
-
userAgent: '*',
|
|
219
|
-
allow: '/',
|
|
220
|
-
disallow: ['/api/', '/admin/', '/private/']
|
|
221
|
-
}
|
|
222
|
-
],
|
|
223
|
-
sitemap: 'https://myapp.com/sitemap.xml'
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
## Canonical URLs
|
|
229
|
-
|
|
230
|
-
```typescript
|
|
231
|
-
// app/blog/[slug]/page.tsx
|
|
232
|
-
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
|
233
|
-
const post = await getPost(params.slug)
|
|
234
|
-
|
|
235
|
-
return {
|
|
236
|
-
alternates: {
|
|
237
|
-
canonical: `https://myapp.com/blog/${params.slug}`,
|
|
238
|
-
languages: {
|
|
239
|
-
'en-US': `https://myapp.com/en/blog/${params.slug}`,
|
|
240
|
-
'es-ES': `https://myapp.com/es/blog/${params.slug}`
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
## When to Use
|
|
248
|
-
|
|
249
|
-
- All public pages
|
|
250
|
-
- Blog posts
|
|
251
|
-
- Product pages
|
|
252
|
-
- Landing pages
|