bsmnt 0.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/.changeset/2026-02-11-test-patch-bump.md +5 -0
- package/.changeset/README.md +10 -0
- package/.changeset/config.json +16 -0
- package/.cursor/rules/README.md +184 -0
- package/.cursor/rules/architecture.mdc +437 -0
- package/.cursor/rules/components.mdc +436 -0
- package/.cursor/rules/integrations.mdc +447 -0
- package/.cursor/rules/main.mdc +278 -0
- package/.cursor/rules/styling.mdc +433 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +14 -0
- package/.github/workflows/.gitkeep +0 -0
- package/.github/workflows/ci.yml +37 -0
- package/.github/workflows/release.yml +54 -0
- package/.tldr/cache/call_graph.json +7 -0
- package/.tldr/languages.json +6 -0
- package/.tldr/status +1 -0
- package/.tldrignore +84 -0
- package/.vscode/extensions.json +20 -0
- package/.vscode/settings.json +98 -0
- package/CHANGELOG.md +13 -0
- package/CLAUDE.md +138 -0
- package/README.md +176 -0
- package/bin/index.js +262 -0
- package/biome.json +44 -0
- package/bun.lock +496 -0
- package/changelog/04-02-26.md +86 -0
- package/changelog/05-02-26.md +101 -0
- package/changelog/09-02-26.md +83 -0
- package/docs/fix-studio-hydration.md +46 -0
- package/docs/plans/2026-01-29-sanity-smart-merge-design.md +196 -0
- package/docs/plans/2026-01-29-sanity-smart-merge-implementation.md +695 -0
- package/docs/sanity-setup-steps.md +199 -0
- package/integrations/basehub/README.md +3 -0
- package/integrations/sanity/app/api/draft-mode/disable/route.ts +7 -0
- package/integrations/sanity/app/api/draft-mode/enable/route.ts +21 -0
- package/integrations/sanity/app/api/revalidate/route.ts +37 -0
- package/integrations/sanity/app/layout.tsx +111 -0
- package/integrations/sanity/app/sitemap.ts +80 -0
- package/integrations/sanity/app/studio/[[...tool]]/page.tsx +8 -0
- package/integrations/sanity/app/studio/layout.tsx +7 -0
- package/integrations/sanity/components/ui/sanity-image/index.tsx +37 -0
- package/integrations/sanity/lib/integrations/README.md +58 -0
- package/integrations/sanity/lib/integrations/check-integration.ts +62 -0
- package/integrations/sanity/lib/integrations/sanity/README.md +144 -0
- package/integrations/sanity/lib/integrations/sanity/client.ts +30 -0
- package/integrations/sanity/lib/integrations/sanity/components/disable-draft-mode.tsx +29 -0
- package/integrations/sanity/lib/integrations/sanity/components/rich-text.tsx +73 -0
- package/integrations/sanity/lib/integrations/sanity/env.ts +38 -0
- package/integrations/sanity/lib/integrations/sanity/live/index.tsx +34 -0
- package/integrations/sanity/lib/integrations/sanity/queries.ts +99 -0
- package/integrations/sanity/lib/integrations/sanity/sanity.cli.ts +20 -0
- package/integrations/sanity/lib/integrations/sanity/sanity.config.ts +94 -0
- package/integrations/sanity/lib/integrations/sanity/sanity.types.ts +337 -0
- package/integrations/sanity/lib/integrations/sanity/schema.json +1850 -0
- package/integrations/sanity/lib/integrations/sanity/schemas/article.ts +132 -0
- package/integrations/sanity/lib/integrations/sanity/schemas/example.ts +203 -0
- package/integrations/sanity/lib/integrations/sanity/schemas/index.ts +37 -0
- package/integrations/sanity/lib/integrations/sanity/schemas/link.ts +127 -0
- package/integrations/sanity/lib/integrations/sanity/schemas/metadata.ts +68 -0
- package/integrations/sanity/lib/integrations/sanity/schemas/navigation.ts +39 -0
- package/integrations/sanity/lib/integrations/sanity/schemas/page.ts +77 -0
- package/integrations/sanity/lib/integrations/sanity/schemas/richText.ts +59 -0
- package/integrations/sanity/lib/integrations/sanity/structure.ts +5 -0
- package/integrations/sanity/lib/integrations/sanity/utils/image.ts +11 -0
- package/integrations/sanity/lib/integrations/sanity/utils/link.ts +61 -0
- package/integrations/sanity/lib/scripts/copy-sanity-mcp.ts +23 -0
- package/integrations/sanity/lib/scripts/generate-page.ts +310 -0
- package/integrations/sanity/lib/utils/metadata.ts +190 -0
- package/layers/experiment/components/layout/header/index.tsx +58 -0
- package/layers/experiment/components/layout/navigation-menu.tsx +127 -0
- package/layers/experiment/lib/constants.ts +12 -0
- package/layers/webgl/app/page.tsx +10 -0
- package/layers/webgl/components/webgl/canvas/dynamic.tsx +34 -0
- package/layers/webgl/components/webgl/canvas/index.tsx +43 -0
- package/layers/webgl/components/webgl/components/scene/index.tsx +21 -0
- package/layers/webgpu/.gitkeep +0 -0
- package/package.json +44 -0
- package/plugins/README.md +21 -0
- package/plugins/no-anchor-element.grit +11 -0
- package/plugins/no-relative-parent-imports.grit +6 -0
- package/plugins/no-unnecessary-forwardref.grit +5 -0
- package/src/commands/add-integration.js +325 -0
- package/src/commands/create.js +415 -0
- package/src/commands/setup-sanity.js +426 -0
- package/src/commands/worktree.js +805 -0
- package/src/mergers/check-integration-merger.js +105 -0
- package/src/mergers/config.js +137 -0
- package/src/mergers/index.js +355 -0
- package/src/mergers/layout-merger.js +223 -0
- package/src/mergers/next-config-merger.js +63 -0
- package/src/mergers/sitemap-merger.js +121 -0
- package/tasks/prd-next-starter-dynamic-layers.md +184 -0
- package/tasks/prd.json +153 -0
- package/tasks/progress.txt +115 -0
- package/template-hooks/use-battery.ts +126 -0
- package/template-hooks/use-device-perf.ts +184 -0
- package/template-hooks/use-intersection-observer.ts +32 -0
- package/template-hooks/use-media.ts +33 -0
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
---
|
|
2
|
+
alwaysApply: true
|
|
3
|
+
---
|
|
4
|
+
---
|
|
5
|
+
description: Third-party integration guidelines (Sanity, Shopify, etc.)
|
|
6
|
+
globs: *.tsx, *.jsx, *.css, *.js, *.ts
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Third-Party Integration Guidelines
|
|
10
|
+
|
|
11
|
+
## Sanity CMS Integration
|
|
12
|
+
|
|
13
|
+
### Configuration & Setup
|
|
14
|
+
Use CDN for performance with stega for visual editing. Store credentials securely in environment variables. All Sanity files are organized in `/lib/integrations/sanity/` directory.
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
// In lib/integrations/sanity/client.ts
|
|
18
|
+
export const client = createClient({
|
|
19
|
+
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,
|
|
20
|
+
dataset: process.env.NEXT_PUBLIC_SANITY_DATASET,
|
|
21
|
+
apiVersion: '2024-03-15',
|
|
22
|
+
useCdn: true, // Use CDN for better performance
|
|
23
|
+
token: process.env.SANITY_API_WRITE_TOKEN, // Write token for editing
|
|
24
|
+
stega: {
|
|
25
|
+
studioUrl: process.env.NEXT_PUBLIC_SANITY_STUDIO_URL || '/studio',
|
|
26
|
+
},
|
|
27
|
+
})
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Schema Management
|
|
31
|
+
|
|
32
|
+
#### Content Modelling
|
|
33
|
+
- Unless explicitly modelling web pages or app views, create content models for what things are, not what they look like in a front-end
|
|
34
|
+
- For example, consider the `status` of an element instead of its `color`
|
|
35
|
+
|
|
36
|
+
#### Basic Schema Types
|
|
37
|
+
- ALWAYS use the `defineType`, `defineField`, and `defineArrayMember` helper functions
|
|
38
|
+
- ALWAYS write schema types to their own files and export a named `const` that matches the filename
|
|
39
|
+
- ONLY use a `name` attribute in fields unless the `title` needs to be something other than a title-case version of the `name`
|
|
40
|
+
- ANY `string` field type with an `options.list` array with fewer than 5 options must use `options.layout: "radio"`
|
|
41
|
+
- ANY `image` field must include `options.hotspot: true`
|
|
42
|
+
- INCLUDE brief, useful `description` values if the intention of a field is not obvious
|
|
43
|
+
- INCLUDE `rule.warning()` for fields that would benefit from being a certain length
|
|
44
|
+
- INCLUDE brief, useful validation errors in `rule.required().error('<Message>')` that signal why the field must be correct before publishing is allowed
|
|
45
|
+
- AVOID `boolean` fields, write a `string` field with an `options.list` configuration
|
|
46
|
+
- NEVER write single `reference` type fields, always write an `array` of references
|
|
47
|
+
- CONSIDER the order of fields, from most important and relevant first, to least often used last
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
// ./src/schemaTypes/lessonType.ts
|
|
51
|
+
import {defineField, defineType} from 'sanity'
|
|
52
|
+
|
|
53
|
+
export const lessonType = defineType({
|
|
54
|
+
name: 'lesson',
|
|
55
|
+
title: 'Lesson',
|
|
56
|
+
type: 'document',
|
|
57
|
+
fields: [
|
|
58
|
+
defineField({
|
|
59
|
+
name: 'title',
|
|
60
|
+
type: 'string',
|
|
61
|
+
}),
|
|
62
|
+
defineField({
|
|
63
|
+
name: 'categories',
|
|
64
|
+
type: 'array',
|
|
65
|
+
of: [defineArrayMember({type: 'reference', to: {type: 'category'}})],
|
|
66
|
+
}),
|
|
67
|
+
],
|
|
68
|
+
})
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
#### Schema Type with Custom Input Components
|
|
72
|
+
If a schema type has input components, they should be colocated with the schema type file. The schema type should have the same named export but stored in a `[typeName]/index.ts` file:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
// ./src/schemaTypes/seoType/index.ts
|
|
76
|
+
import {defineField, defineType} from 'sanity'
|
|
77
|
+
import seoInput from './seoInput'
|
|
78
|
+
|
|
79
|
+
export const seoType = defineType({
|
|
80
|
+
name: 'seo',
|
|
81
|
+
title: 'SEO',
|
|
82
|
+
type: 'object',
|
|
83
|
+
components: { input: seoInput }
|
|
84
|
+
// ...
|
|
85
|
+
})
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
#### No Anonymous Reusable Schema Types
|
|
89
|
+
ANY schema type that benefits from being reused in multiple document types should be registered as its own custom schema type.
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
// ./src/schemaTypes/blockContentType.ts
|
|
93
|
+
import {defineField, defineType} from 'sanity'
|
|
94
|
+
|
|
95
|
+
export const blockContentType = defineType({
|
|
96
|
+
name: 'blockContent',
|
|
97
|
+
title: 'Block content',
|
|
98
|
+
type: 'array',
|
|
99
|
+
of: [defineField({name: 'block',type: 'block'})],
|
|
100
|
+
})
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
#### Decorating Schema Types
|
|
104
|
+
Every `document` and `object` schema type should:
|
|
105
|
+
|
|
106
|
+
- Have an `icon` property from `@sanity/icons`
|
|
107
|
+
- Have a customized `preview` property that shows rich contextual details about the document
|
|
108
|
+
- Use `groups` when the schema type has more than a few fields to collate related fields and only show the most important group by default. These `groups` should use the icon property as well.
|
|
109
|
+
- Use `fieldsets` with `options: {columns: 2}` if related fields could be grouped visually together, such as `startDate` and `endDate`
|
|
110
|
+
|
|
111
|
+
### Visual Editing
|
|
112
|
+
Always add `data-sanity` attributes for visual editing. Use SanityContextProvider for document access. Implement proper draft mode handling. Import from `/lib/integrations/sanity` directory.
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import { RichText } from '@/integrations/sanity/components/rich-text'
|
|
116
|
+
|
|
117
|
+
export function MyComponent() {
|
|
118
|
+
const { document } = useSanityContext()
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<div data-sanity={document._id}>
|
|
122
|
+
<h1 data-sanity="title">{document.title}</h1>
|
|
123
|
+
<div data-sanity="content">
|
|
124
|
+
<RichText content={document.content} />
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Data Fetching
|
|
132
|
+
Use proper perspective for draft vs published content. Implement caching strategies for performance. Handle errors gracefully with try-catch. Import from `/lib/integrations/sanity` directory. Use `@/utils/metadata` helpers for SEO optimization. All `sanityFetch` calls automatically use `cacheSignal()` for request cleanup.
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// In lib/integrations/sanity/queries/index.ts
|
|
136
|
+
import { sanityFetch } from '@/integrations/sanity/live'
|
|
137
|
+
import { generateSanityMetadata } from '@/utils/metadata'
|
|
138
|
+
|
|
139
|
+
// Use sanityFetch (automatically includes cacheSignal)
|
|
140
|
+
export async function fetchSanityPage(slug: string, isDraftMode = false) {
|
|
141
|
+
const { data, error } = await sanityFetch({
|
|
142
|
+
query: pageQuery,
|
|
143
|
+
params: { slug },
|
|
144
|
+
isDraftMode
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
if (error) {
|
|
148
|
+
console.error('fetchSanityPage error:', error)
|
|
149
|
+
return { data: null, error }
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return { data, error: null }
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// In page.tsx for SEO
|
|
156
|
+
export async function generateMetadata({ params }) {
|
|
157
|
+
const { data } = await fetchSanityPage(params.slug)
|
|
158
|
+
return generateSanityMetadata(data)
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Cache Components Notes:**
|
|
163
|
+
- Draft mode automatically uses `cache: 'no-store'` ✅
|
|
164
|
+
- Published content uses ISR with revalidation ✅
|
|
165
|
+
- All queries use `cacheSignal()` for automatic cleanup ✅
|
|
166
|
+
- Wrap in Suspense boundaries for proper loading states ✅
|
|
167
|
+
|
|
168
|
+
### GROQ Queries
|
|
169
|
+
|
|
170
|
+
- ALWAYS use SCREAMING_SNAKE_CASE for variable names, for example POSTS_QUERY
|
|
171
|
+
- ALWAYS write queries to their own variables, never as a parameter in a function
|
|
172
|
+
- ALWAYS import the `defineQuery` function to wrap query strings from the `groq` or `next-sanity` package
|
|
173
|
+
- ALWAYS write every required attribute in a projection when writing a query
|
|
174
|
+
- ALWAYS put each segment of a filter, and each attribute on its own line
|
|
175
|
+
- ALWAYS use parameters for variables in a query
|
|
176
|
+
- NEVER insert dynamic values using string interpolation
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
// In lib/integrations/sanity/queries/index.ts
|
|
180
|
+
import { groq } from 'next-sanity'
|
|
181
|
+
|
|
182
|
+
export const pageQuery = groq`
|
|
183
|
+
*[_type == "page" && slug.current == $slug][0] {
|
|
184
|
+
_id,
|
|
185
|
+
title,
|
|
186
|
+
slug,
|
|
187
|
+
content,
|
|
188
|
+
"imageUrl": image.asset->url,
|
|
189
|
+
_updatedAt
|
|
190
|
+
}
|
|
191
|
+
`
|
|
192
|
+
|
|
193
|
+
// Good query example
|
|
194
|
+
import {defineQuery} from 'groq'
|
|
195
|
+
|
|
196
|
+
export const POST_QUERY = defineQuery(`*[
|
|
197
|
+
_type == "post"
|
|
198
|
+
&& slug.current == $slug
|
|
199
|
+
][0]{
|
|
200
|
+
_id,
|
|
201
|
+
title,
|
|
202
|
+
image,
|
|
203
|
+
author->{
|
|
204
|
+
_id,
|
|
205
|
+
name
|
|
206
|
+
}
|
|
207
|
+
}`)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Performance Optimization
|
|
211
|
+
Use ISR for static content with revalidation. Implement proper cache invalidation via webhooks. Optimize images with proper sizing. Use consolidated imports for better tree-shaking. Use metadata helpers for consistent SEO.
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
import { urlForImage } from '@/integrations/sanity/utils/image'
|
|
215
|
+
import { generateSanityMetadata } from '@/utils/metadata'
|
|
216
|
+
|
|
217
|
+
// Use ISR for published content
|
|
218
|
+
export const revalidate = 3600
|
|
219
|
+
|
|
220
|
+
// Optimize images
|
|
221
|
+
<SanityImage image={document.image} maxWidth={1200} />
|
|
222
|
+
|
|
223
|
+
// Generate SEO metadata
|
|
224
|
+
export async function generateMetadata({ params }) {
|
|
225
|
+
const page = await fetchSanityPage(params.slug)
|
|
226
|
+
return generateSanityMetadata(page, {
|
|
227
|
+
title: page.seo?.title || page.title,
|
|
228
|
+
description: page.seo?.description,
|
|
229
|
+
image: page.seo?.image || page.image,
|
|
230
|
+
noIndex: page.seo?.noIndex,
|
|
231
|
+
})
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Project Structure
|
|
236
|
+
All Sanity files are organized in `/lib/integrations/sanity/` directory
|
|
237
|
+
|
|
238
|
+
```
|
|
239
|
+
lib/integrations/sanity/
|
|
240
|
+
├── sanity.cli.ts # CLI configuration
|
|
241
|
+
├── sanity.config.ts # Studio configuration
|
|
242
|
+
├── env.ts # Environment variables
|
|
243
|
+
├── structure.ts # Studio structure
|
|
244
|
+
├── client.ts # Sanity client
|
|
245
|
+
├── fetch.ts # Data fetching with cacheSignal
|
|
246
|
+
├── index.ts # Main exports
|
|
247
|
+
├── README.md # Documentation
|
|
248
|
+
├── queries/
|
|
249
|
+
│ └── index.ts # GROQ queries
|
|
250
|
+
├── schemaTypes/ # Content type definitions
|
|
251
|
+
│ ├── documents/ # Document types (article, page, etc.)
|
|
252
|
+
│ ├── objects/ # Object types (link, metadata, richText)
|
|
253
|
+
│ ├── singletons/ # Singleton types (navigation)
|
|
254
|
+
│ └── index.ts
|
|
255
|
+
├── components/ # React components
|
|
256
|
+
│ ├── context.tsx # React context
|
|
257
|
+
│ ├── rich-text.tsx # Rich text component
|
|
258
|
+
│ └── disable-draft-mode.tsx
|
|
259
|
+
└── utils/
|
|
260
|
+
├── image.ts # Image utilities
|
|
261
|
+
└── link.ts # Link utilities
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Writing Sanity Content for Importing
|
|
265
|
+
|
|
266
|
+
When asked to write content:
|
|
267
|
+
|
|
268
|
+
- ONLY use the existing schema types registered in the Studio configuration
|
|
269
|
+
- ALWAYS write content as an `.ndjson` file at the root of the project
|
|
270
|
+
- NEVER write a script to write the file, just write the file
|
|
271
|
+
- IMPORT `.ndjson` files using the CLI command `npx sanity dataset import <filename.ndjson>`
|
|
272
|
+
- NEVER include a `.` in the `_id` field of a document unless you need it to be private
|
|
273
|
+
- NEVER include image references because you don't know what image documents exist
|
|
274
|
+
- ALWAYS write images in this format below, replacing the document ID value to generate the same placeholder image
|
|
275
|
+
|
|
276
|
+
```JSON
|
|
277
|
+
{"_type":"image","_sanityAsset":"image@https://picsum.photos/seed/[[REPLACE_WITH_DOCUMENT_ID]]/1920/1080"}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### TypeScript Generation
|
|
281
|
+
|
|
282
|
+
#### For the Studio
|
|
283
|
+
ALWAYS re-run schema extraction after making schema file changes with `npx sanity@latest schema extract`
|
|
284
|
+
|
|
285
|
+
#### For Monorepos
|
|
286
|
+
ALWAYS use a simple pnpm workspace configuration to place the studio in `apps/studio`
|
|
287
|
+
|
|
288
|
+
```
|
|
289
|
+
your-project/
|
|
290
|
+
└── apps/
|
|
291
|
+
├── studio/ -> Sanity Studio
|
|
292
|
+
└── web/ -> Front-end
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
- ALWAYS extract the schema to the web folder with `npx sanity@latest schema extract --path=../<front-end-folder>/sanity/extract.json`
|
|
296
|
+
- ALWAYS generate types with `npx sanity@latest typegen generate` after every GROQ query change
|
|
297
|
+
- ALWAYS create a TypeGen configuration file called `sanity-typegen.json` at the root of the front-end code-base
|
|
298
|
+
|
|
299
|
+
```json
|
|
300
|
+
{
|
|
301
|
+
"path": "./**/*.{ts,tsx,js,jsx}",
|
|
302
|
+
"schema": "./<front-end-folder>/sanity/extract.json",
|
|
303
|
+
"generates": "./<web-folder>/sanity/types.ts"
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
#### For the Front-end
|
|
308
|
+
ONLY write Types for document types and query responses if you cannot generate them with Sanity TypeGen
|
|
309
|
+
|
|
310
|
+
### Project Settings and Data
|
|
311
|
+
ALWAYS check if there is a way to interact with a project via the CLI before writing custom scripts `npx sanity --help`
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Shopify Integration
|
|
316
|
+
|
|
317
|
+
### API Configuration
|
|
318
|
+
Use GraphQL for queries. Store credentials securely.
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
const shopifyClient = createShopifyClient({
|
|
322
|
+
domain: process.env.SHOPIFY_DOMAIN,
|
|
323
|
+
storefrontAccessToken: process.env.SHOPIFY_STOREFRONT_TOKEN
|
|
324
|
+
})
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Product Management
|
|
328
|
+
Use fragments for reusable queries. Implement proper error handling.
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
import { PRODUCT_FRAGMENT } from '@/lib/integrations/shopify/fragments/product'
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Cart Operations
|
|
335
|
+
Use mutations for cart operations. Maintain cart state with Zustand.
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
import { ADD_TO_CART_MUTATION } from '@/lib/integrations/shopify/mutations/cart'
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## General Integration Best Practices
|
|
344
|
+
|
|
345
|
+
### Environment Variables
|
|
346
|
+
- Never commit API keys
|
|
347
|
+
- Use `.env.local` for development
|
|
348
|
+
- Document required variables in `.env.example`
|
|
349
|
+
- Validate environment variables are set before use
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
// Check required env vars at module load
|
|
353
|
+
const requiredEnvVars = ['NEXT_PUBLIC_API_KEY', 'API_SECRET'] as const
|
|
354
|
+
for (const envVar of requiredEnvVars) {
|
|
355
|
+
if (!process.env[envVar]) {
|
|
356
|
+
throw new Error(`Missing required environment variable: ${envVar}`)
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### API Resilience
|
|
362
|
+
**Always use `fetchWithTimeout` for external API calls**
|
|
363
|
+
|
|
364
|
+
- Standard timeouts: 8-10 seconds for most integrations
|
|
365
|
+
- Implement proper error handling and retry logic
|
|
366
|
+
- Use `cacheSignal()` for automatic request cleanup (React 19+)
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
import { fetchWithTimeout } from '@/utils/fetch'
|
|
370
|
+
import { cacheSignal } from 'react'
|
|
371
|
+
|
|
372
|
+
// With cacheSignal for automatic cleanup
|
|
373
|
+
const signal = cacheSignal()
|
|
374
|
+
const response = await fetchWithTimeout(url, {
|
|
375
|
+
timeout: 10000,
|
|
376
|
+
signal: signal as AbortSignal,
|
|
377
|
+
...options
|
|
378
|
+
})
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### Error Handling
|
|
382
|
+
Implement timeout handling for all API calls. Provide user-friendly error messages. Log errors for debugging with context.
|
|
383
|
+
|
|
384
|
+
```typescript
|
|
385
|
+
try {
|
|
386
|
+
const response = await fetchWithTimeout(url, options, 10000)
|
|
387
|
+
return { data: await response.json(), error: null }
|
|
388
|
+
} catch (error) {
|
|
389
|
+
console.error('API call failed:', error)
|
|
390
|
+
return { data: null, error: error.message }
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### Type Safety
|
|
395
|
+
Generate TypeScript types from APIs when possible. Use proper validation (e.g., Zod schemas).
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
import * as z from "zod";
|
|
399
|
+
|
|
400
|
+
const ProductSchema = z.object({
|
|
401
|
+
id: z.string(),
|
|
402
|
+
title: z.string(),
|
|
403
|
+
price: z.number()
|
|
404
|
+
})
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Performance
|
|
408
|
+
- Cache API responses appropriately (except user-specific data)
|
|
409
|
+
- Use ISR (Incremental Static Regeneration) for dynamic content
|
|
410
|
+
- Implement proper loading states with Suspense boundaries
|
|
411
|
+
- Use `cacheSignal()` for automatic request cleanup
|
|
412
|
+
|
|
413
|
+
**⚠️ Cache Components Gotchas:**
|
|
414
|
+
- **User-specific data**: Never cache (carts, accounts, private content)
|
|
415
|
+
- **Real-time data**: Use `cache: 'no-store'` for live feeds
|
|
416
|
+
- **Suspense boundaries**: Required for cached data fetching
|
|
417
|
+
- **Cache invalidation**: Use `revalidateTag()` or `revalidatePath()` in webhooks
|
|
418
|
+
- **Testing**: Test with hard refresh AND navigation (different cache layers)
|
|
419
|
+
|
|
420
|
+
### Security
|
|
421
|
+
- Validate all user inputs
|
|
422
|
+
- Use server-side API calls for sensitive operations
|
|
423
|
+
- Implement rate limiting where necessary
|
|
424
|
+
|
|
425
|
+
### Integration Management
|
|
426
|
+
- Check integration usage with `@/lib/integrations/check-integration`
|
|
427
|
+
- Keep only active integrations to optimize bundle size
|
|
428
|
+
- Remove unused integration directories to reduce bundle size
|
|
429
|
+
|
|
430
|
+
## Webhook Handling
|
|
431
|
+
|
|
432
|
+
### Verification
|
|
433
|
+
Always verify webhook signatures. Use proper authentication.
|
|
434
|
+
|
|
435
|
+
```typescript
|
|
436
|
+
export async function verifyWebhookSignature(
|
|
437
|
+
payload: string,
|
|
438
|
+
signature: string
|
|
439
|
+
): Promise<boolean> {
|
|
440
|
+
// verification logic
|
|
441
|
+
}
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### Processing
|
|
445
|
+
Process webhooks asynchronously. Implement idempotency. Return 200 status quickly.
|
|
446
|
+
|
|
447
|
+
Last updated: 2025-12-18
|