@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,266 +0,0 @@
|
|
|
1
|
-
# Incremental Static Regeneration Patterns
|
|
2
|
-
|
|
3
|
-
Patterns for ISR with Next.js.
|
|
4
|
-
|
|
5
|
-
## Basic ISR
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
// app/posts/[slug]/page.tsx
|
|
9
|
-
|
|
10
|
-
// Generate static pages at build time
|
|
11
|
-
export async function generateStaticParams() {
|
|
12
|
-
const posts = await getPosts()
|
|
13
|
-
return posts.map(post => ({ slug: post.slug }))
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// Revalidate every hour
|
|
17
|
-
export const revalidate = 3600
|
|
18
|
-
|
|
19
|
-
export default async function PostPage({
|
|
20
|
-
params
|
|
21
|
-
}: {
|
|
22
|
-
params: { slug: string }
|
|
23
|
-
}) {
|
|
24
|
-
const post = await getPost(params.slug)
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<article>
|
|
28
|
-
<h1>{post.title}</h1>
|
|
29
|
-
<div>{post.content}</div>
|
|
30
|
-
</article>
|
|
31
|
-
)
|
|
32
|
-
}
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## On-Demand Revalidation
|
|
36
|
-
|
|
37
|
-
```typescript
|
|
38
|
-
// app/api/revalidate/route.ts
|
|
39
|
-
import { revalidatePath, revalidateTag } from 'next/cache'
|
|
40
|
-
import { NextRequest } from 'next/server'
|
|
41
|
-
|
|
42
|
-
export async function POST(request: NextRequest) {
|
|
43
|
-
const { secret, path, tag } = await request.json()
|
|
44
|
-
|
|
45
|
-
// Verify secret
|
|
46
|
-
if (secret !== process.env.REVALIDATE_SECRET) {
|
|
47
|
-
return Response.json({ error: 'Invalid secret' }, { status: 401 })
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
if (tag) {
|
|
52
|
-
revalidateTag(tag)
|
|
53
|
-
return Response.json({ revalidated: true, tag })
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (path) {
|
|
57
|
-
revalidatePath(path)
|
|
58
|
-
return Response.json({ revalidated: true, path })
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return Response.json({ error: 'Missing path or tag' }, { status: 400 })
|
|
62
|
-
} catch (error) {
|
|
63
|
-
return Response.json({ error: 'Revalidation failed' }, { status: 500 })
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Trigger from CMS webhook or after content update
|
|
68
|
-
async function revalidatePost(slug: string) {
|
|
69
|
-
await fetch(`${process.env.NEXT_PUBLIC_APP_URL}/api/revalidate`, {
|
|
70
|
-
method: 'POST',
|
|
71
|
-
headers: { 'Content-Type': 'application/json' },
|
|
72
|
-
body: JSON.stringify({
|
|
73
|
-
secret: process.env.REVALIDATE_SECRET,
|
|
74
|
-
path: `/posts/${slug}`
|
|
75
|
-
})
|
|
76
|
-
})
|
|
77
|
-
}
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
## Tag-Based Revalidation
|
|
81
|
-
|
|
82
|
-
```typescript
|
|
83
|
-
// lib/posts.ts
|
|
84
|
-
import { unstable_cache } from 'next/cache'
|
|
85
|
-
|
|
86
|
-
export const getPosts = unstable_cache(
|
|
87
|
-
async () => {
|
|
88
|
-
return prisma.post.findMany({
|
|
89
|
-
where: { published: true },
|
|
90
|
-
orderBy: { createdAt: 'desc' }
|
|
91
|
-
})
|
|
92
|
-
},
|
|
93
|
-
['posts'],
|
|
94
|
-
{ tags: ['posts'], revalidate: 3600 }
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
export const getPost = unstable_cache(
|
|
98
|
-
async (slug: string) => {
|
|
99
|
-
return prisma.post.findUnique({
|
|
100
|
-
where: { slug },
|
|
101
|
-
include: { author: true }
|
|
102
|
-
})
|
|
103
|
-
},
|
|
104
|
-
['post'],
|
|
105
|
-
{ tags: ['posts'], revalidate: 3600 }
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
// When a post is updated
|
|
109
|
-
export async function updatePost(id: string, data: PostData) {
|
|
110
|
-
await prisma.post.update({ where: { id }, data })
|
|
111
|
-
|
|
112
|
-
// Revalidate all post-related pages
|
|
113
|
-
revalidateTag('posts')
|
|
114
|
-
}
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
## Time-Based vs On-Demand
|
|
118
|
-
|
|
119
|
-
```typescript
|
|
120
|
-
// Time-based: Good for frequently changing data
|
|
121
|
-
// app/news/page.tsx
|
|
122
|
-
export const revalidate = 60 // Every minute
|
|
123
|
-
|
|
124
|
-
// On-demand: Good for user-triggered updates
|
|
125
|
-
// After user creates/edits content
|
|
126
|
-
async function saveContent(data: ContentData) {
|
|
127
|
-
const content = await prisma.content.create({ data })
|
|
128
|
-
|
|
129
|
-
// Immediately revalidate
|
|
130
|
-
revalidatePath(`/content/${content.slug}`)
|
|
131
|
-
|
|
132
|
-
return content
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Hybrid: Time-based with on-demand override
|
|
136
|
-
// app/products/page.tsx
|
|
137
|
-
export const revalidate = 3600 // Hourly background refresh
|
|
138
|
-
|
|
139
|
-
// But also revalidate immediately when inventory changes
|
|
140
|
-
async function updateInventory(productId: string, quantity: number) {
|
|
141
|
-
await prisma.product.update({
|
|
142
|
-
where: { id: productId },
|
|
143
|
-
data: { quantity }
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
revalidatePath('/products')
|
|
147
|
-
revalidatePath(`/products/${productId}`)
|
|
148
|
-
}
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
## Dynamic Routes with Fallback
|
|
152
|
-
|
|
153
|
-
```typescript
|
|
154
|
-
// app/products/[id]/page.tsx
|
|
155
|
-
|
|
156
|
-
export async function generateStaticParams() {
|
|
157
|
-
// Only pre-render popular products
|
|
158
|
-
const popular = await prisma.product.findMany({
|
|
159
|
-
where: { featured: true },
|
|
160
|
-
select: { id: true }
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
return popular.map(p => ({ id: p.id }))
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// New products render on first request, then cached
|
|
167
|
-
export const dynamicParams = true
|
|
168
|
-
|
|
169
|
-
// Or block until generated
|
|
170
|
-
// export const dynamicParams = 'blocking'
|
|
171
|
-
|
|
172
|
-
export default async function ProductPage({
|
|
173
|
-
params
|
|
174
|
-
}: {
|
|
175
|
-
params: { id: string }
|
|
176
|
-
}) {
|
|
177
|
-
const product = await getProduct(params.id)
|
|
178
|
-
|
|
179
|
-
if (!product) {
|
|
180
|
-
notFound()
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return <ProductDetails product={product} />
|
|
184
|
-
}
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
## Streaming with ISR
|
|
188
|
-
|
|
189
|
-
```tsx
|
|
190
|
-
// app/dashboard/page.tsx
|
|
191
|
-
import { Suspense } from 'react'
|
|
192
|
-
|
|
193
|
-
export const revalidate = 60
|
|
194
|
-
|
|
195
|
-
export default async function DashboardPage() {
|
|
196
|
-
// This data is cached and revalidated
|
|
197
|
-
const user = await getUser()
|
|
198
|
-
|
|
199
|
-
return (
|
|
200
|
-
<div>
|
|
201
|
-
<h1>Welcome, {user.name}</h1>
|
|
202
|
-
|
|
203
|
-
{/* Stream in slower data */}
|
|
204
|
-
<Suspense fallback={<StatsSkeleton />}>
|
|
205
|
-
<Stats userId={user.id} />
|
|
206
|
-
</Suspense>
|
|
207
|
-
|
|
208
|
-
<Suspense fallback={<ActivitySkeleton />}>
|
|
209
|
-
<RecentActivity userId={user.id} />
|
|
210
|
-
</Suspense>
|
|
211
|
-
</div>
|
|
212
|
-
)
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Each component can have its own cache settings
|
|
216
|
-
async function Stats({ userId }: { userId: string }) {
|
|
217
|
-
const stats = await getCachedStats(userId)
|
|
218
|
-
return <StatsDisplay stats={stats} />
|
|
219
|
-
}
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
## CMS Integration
|
|
223
|
-
|
|
224
|
-
```typescript
|
|
225
|
-
// app/api/cms-webhook/route.ts
|
|
226
|
-
// Webhook handler for headless CMS
|
|
227
|
-
|
|
228
|
-
import { revalidatePath, revalidateTag } from 'next/cache'
|
|
229
|
-
|
|
230
|
-
export async function POST(request: Request) {
|
|
231
|
-
const payload = await request.json()
|
|
232
|
-
|
|
233
|
-
// Verify webhook signature
|
|
234
|
-
const signature = request.headers.get('x-webhook-signature')
|
|
235
|
-
if (!verifySignature(payload, signature)) {
|
|
236
|
-
return Response.json({ error: 'Invalid signature' }, { status: 401 })
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Handle different content types
|
|
240
|
-
switch (payload.type) {
|
|
241
|
-
case 'post.published':
|
|
242
|
-
case 'post.updated':
|
|
243
|
-
revalidatePath(`/blog/${payload.data.slug}`)
|
|
244
|
-
revalidateTag('posts')
|
|
245
|
-
break
|
|
246
|
-
|
|
247
|
-
case 'page.updated':
|
|
248
|
-
revalidatePath(`/${payload.data.slug}`)
|
|
249
|
-
break
|
|
250
|
-
|
|
251
|
-
case 'settings.updated':
|
|
252
|
-
// Revalidate entire site
|
|
253
|
-
revalidatePath('/', 'layout')
|
|
254
|
-
break
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
return Response.json({ success: true })
|
|
258
|
-
}
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
## When to Use
|
|
262
|
-
|
|
263
|
-
- Blog posts
|
|
264
|
-
- Product pages
|
|
265
|
-
- Marketing pages
|
|
266
|
-
- Documentation
|
|
@@ -1,281 +0,0 @@
|
|
|
1
|
-
# Lazy Loading Patterns
|
|
2
|
-
|
|
3
|
-
Patterns for lazy loading components and data.
|
|
4
|
-
|
|
5
|
-
## Dynamic Imports
|
|
6
|
-
|
|
7
|
-
```tsx
|
|
8
|
-
// app/page.tsx
|
|
9
|
-
import dynamic from 'next/dynamic'
|
|
10
|
-
|
|
11
|
-
// Basic dynamic import
|
|
12
|
-
const HeavyChart = dynamic(() => import('@/components/HeavyChart'), {
|
|
13
|
-
loading: () => <div className="h-64 animate-pulse bg-gray-200" />
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
// With SSR disabled (client-only component)
|
|
17
|
-
const MapComponent = dynamic(
|
|
18
|
-
() => import('@/components/Map'),
|
|
19
|
-
{ ssr: false }
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
// Named exports
|
|
23
|
-
const Modal = dynamic(
|
|
24
|
-
() => import('@/components/Modals').then(mod => mod.Modal)
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
export default function Page() {
|
|
28
|
-
return (
|
|
29
|
-
<div>
|
|
30
|
-
<HeavyChart data={chartData} />
|
|
31
|
-
<MapComponent location={location} />
|
|
32
|
-
</div>
|
|
33
|
-
)
|
|
34
|
-
}
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
## React.lazy with Suspense
|
|
38
|
-
|
|
39
|
-
```tsx
|
|
40
|
-
// components/Dashboard.tsx
|
|
41
|
-
import { Suspense, lazy } from 'react'
|
|
42
|
-
|
|
43
|
-
const Analytics = lazy(() => import('./Analytics'))
|
|
44
|
-
const Reports = lazy(() => import('./Reports'))
|
|
45
|
-
const Settings = lazy(() => import('./Settings'))
|
|
46
|
-
|
|
47
|
-
export function Dashboard({ tab }: { tab: string }) {
|
|
48
|
-
return (
|
|
49
|
-
<div>
|
|
50
|
-
<Suspense fallback={<TabSkeleton />}>
|
|
51
|
-
{tab === 'analytics' && <Analytics />}
|
|
52
|
-
{tab === 'reports' && <Reports />}
|
|
53
|
-
{tab === 'settings' && <Settings />}
|
|
54
|
-
</Suspense>
|
|
55
|
-
</div>
|
|
56
|
-
)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function TabSkeleton() {
|
|
60
|
-
return (
|
|
61
|
-
<div className="space-y-4 p-4">
|
|
62
|
-
<div className="h-8 w-1/3 animate-pulse rounded bg-gray-200" />
|
|
63
|
-
<div className="h-64 animate-pulse rounded bg-gray-200" />
|
|
64
|
-
</div>
|
|
65
|
-
)
|
|
66
|
-
}
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
## Intersection Observer
|
|
70
|
-
|
|
71
|
-
```tsx
|
|
72
|
-
// components/LazySection.tsx
|
|
73
|
-
'use client'
|
|
74
|
-
|
|
75
|
-
import { useRef, useEffect, useState, ReactNode } from 'react'
|
|
76
|
-
|
|
77
|
-
interface Props {
|
|
78
|
-
children: ReactNode
|
|
79
|
-
fallback?: ReactNode
|
|
80
|
-
rootMargin?: string
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function LazySection({
|
|
84
|
-
children,
|
|
85
|
-
fallback = <div className="h-64" />,
|
|
86
|
-
rootMargin = '100px'
|
|
87
|
-
}: Props) {
|
|
88
|
-
const ref = useRef<HTMLDivElement>(null)
|
|
89
|
-
const [isVisible, setIsVisible] = useState(false)
|
|
90
|
-
|
|
91
|
-
useEffect(() => {
|
|
92
|
-
const observer = new IntersectionObserver(
|
|
93
|
-
([entry]) => {
|
|
94
|
-
if (entry.isIntersecting) {
|
|
95
|
-
setIsVisible(true)
|
|
96
|
-
observer.disconnect()
|
|
97
|
-
}
|
|
98
|
-
},
|
|
99
|
-
{ rootMargin }
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
if (ref.current) {
|
|
103
|
-
observer.observe(ref.current)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return () => observer.disconnect()
|
|
107
|
-
}, [rootMargin])
|
|
108
|
-
|
|
109
|
-
return (
|
|
110
|
-
<div ref={ref}>
|
|
111
|
-
{isVisible ? children : fallback}
|
|
112
|
-
</div>
|
|
113
|
-
)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Usage
|
|
117
|
-
<LazySection>
|
|
118
|
-
<HeavyComponent />
|
|
119
|
-
</LazySection>
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
## Lazy Image Loading
|
|
123
|
-
|
|
124
|
-
```tsx
|
|
125
|
-
// components/LazyImage.tsx
|
|
126
|
-
'use client'
|
|
127
|
-
|
|
128
|
-
import Image from 'next/image'
|
|
129
|
-
import { useState } from 'react'
|
|
130
|
-
|
|
131
|
-
interface Props {
|
|
132
|
-
src: string
|
|
133
|
-
alt: string
|
|
134
|
-
width: number
|
|
135
|
-
height: number
|
|
136
|
-
className?: string
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export function LazyImage({ src, alt, width, height, className }: Props) {
|
|
140
|
-
const [isLoaded, setIsLoaded] = useState(false)
|
|
141
|
-
|
|
142
|
-
return (
|
|
143
|
-
<div className={`relative ${className}`}>
|
|
144
|
-
{/* Blur placeholder */}
|
|
145
|
-
{!isLoaded && (
|
|
146
|
-
<div
|
|
147
|
-
className="absolute inset-0 animate-pulse bg-gray-200"
|
|
148
|
-
style={{ aspectRatio: `${width}/${height}` }}
|
|
149
|
-
/>
|
|
150
|
-
)}
|
|
151
|
-
|
|
152
|
-
<Image
|
|
153
|
-
src={src}
|
|
154
|
-
alt={alt}
|
|
155
|
-
width={width}
|
|
156
|
-
height={height}
|
|
157
|
-
loading="lazy"
|
|
158
|
-
onLoad={() => setIsLoaded(true)}
|
|
159
|
-
className={`transition-opacity duration-300 ${
|
|
160
|
-
isLoaded ? 'opacity-100' : 'opacity-0'
|
|
161
|
-
}`}
|
|
162
|
-
/>
|
|
163
|
-
</div>
|
|
164
|
-
)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// With blur placeholder
|
|
168
|
-
export function BlurImage({ src, alt, ...props }: Props & { blurDataUrl: string }) {
|
|
169
|
-
return (
|
|
170
|
-
<Image
|
|
171
|
-
src={src}
|
|
172
|
-
alt={alt}
|
|
173
|
-
{...props}
|
|
174
|
-
loading="lazy"
|
|
175
|
-
placeholder="blur"
|
|
176
|
-
blurDataURL={props.blurDataUrl}
|
|
177
|
-
/>
|
|
178
|
-
)
|
|
179
|
-
}
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
## Virtual List
|
|
183
|
-
|
|
184
|
-
```tsx
|
|
185
|
-
// components/VirtualList.tsx
|
|
186
|
-
'use client'
|
|
187
|
-
|
|
188
|
-
import { useVirtualizer } from '@tanstack/react-virtual'
|
|
189
|
-
import { useRef } from 'react'
|
|
190
|
-
|
|
191
|
-
interface Props<T> {
|
|
192
|
-
items: T[]
|
|
193
|
-
itemHeight: number
|
|
194
|
-
renderItem: (item: T, index: number) => React.ReactNode
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
export function VirtualList<T>({ items, itemHeight, renderItem }: Props<T>) {
|
|
198
|
-
const parentRef = useRef<HTMLDivElement>(null)
|
|
199
|
-
|
|
200
|
-
const virtualizer = useVirtualizer({
|
|
201
|
-
count: items.length,
|
|
202
|
-
getScrollElement: () => parentRef.current,
|
|
203
|
-
estimateSize: () => itemHeight,
|
|
204
|
-
overscan: 5
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
return (
|
|
208
|
-
<div
|
|
209
|
-
ref={parentRef}
|
|
210
|
-
className="h-[500px] overflow-auto"
|
|
211
|
-
>
|
|
212
|
-
<div
|
|
213
|
-
style={{
|
|
214
|
-
height: `${virtualizer.getTotalSize()}px`,
|
|
215
|
-
position: 'relative'
|
|
216
|
-
}}
|
|
217
|
-
>
|
|
218
|
-
{virtualizer.getVirtualItems().map(virtualItem => (
|
|
219
|
-
<div
|
|
220
|
-
key={virtualItem.key}
|
|
221
|
-
style={{
|
|
222
|
-
position: 'absolute',
|
|
223
|
-
top: 0,
|
|
224
|
-
left: 0,
|
|
225
|
-
width: '100%',
|
|
226
|
-
height: `${virtualItem.size}px`,
|
|
227
|
-
transform: `translateY(${virtualItem.start}px)`
|
|
228
|
-
}}
|
|
229
|
-
>
|
|
230
|
-
{renderItem(items[virtualItem.index], virtualItem.index)}
|
|
231
|
-
</div>
|
|
232
|
-
))}
|
|
233
|
-
</div>
|
|
234
|
-
</div>
|
|
235
|
-
)
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Usage
|
|
239
|
-
<VirtualList
|
|
240
|
-
items={largeDataset}
|
|
241
|
-
itemHeight={60}
|
|
242
|
-
renderItem={(item) => <ListItem item={item} />}
|
|
243
|
-
/>
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
## Conditional Feature Loading
|
|
247
|
-
|
|
248
|
-
```tsx
|
|
249
|
-
// components/FeatureLoader.tsx
|
|
250
|
-
'use client'
|
|
251
|
-
|
|
252
|
-
import dynamic from 'next/dynamic'
|
|
253
|
-
|
|
254
|
-
const features = {
|
|
255
|
-
analytics: dynamic(() => import('@/features/Analytics')),
|
|
256
|
-
collaboration: dynamic(() => import('@/features/Collaboration')),
|
|
257
|
-
aiAssistant: dynamic(() => import('@/features/AIAssistant'))
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
interface Props {
|
|
261
|
-
enabledFeatures: (keyof typeof features)[]
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
export function FeatureLoader({ enabledFeatures }: Props) {
|
|
265
|
-
return (
|
|
266
|
-
<>
|
|
267
|
-
{enabledFeatures.map(feature => {
|
|
268
|
-
const Component = features[feature]
|
|
269
|
-
return <Component key={feature} />
|
|
270
|
-
})}
|
|
271
|
-
</>
|
|
272
|
-
)
|
|
273
|
-
}
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
## When to Use
|
|
277
|
-
|
|
278
|
-
- Heavy components
|
|
279
|
-
- Below-the-fold content
|
|
280
|
-
- Feature flags
|
|
281
|
-
- Large lists
|