@mikulgohil/ai-kit 1.8.0 → 1.9.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/README.md +208 -141
- package/dist/index.js +78 -7
- package/dist/index.js.map +1 -1
- package/package.json +9 -2
- package/templates/claude-md/optimizely-saas.md +384 -0
- package/templates/cursorrules/optimizely-saas.md +20 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikulgohil/ai-kit",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "AI-assisted development setup kit. Auto-detects your tech stack and generates tailored CLAUDE.md, .cursorrules, hooks, agents, context modes, slash commands, design token rules, component registries, developer guides,
|
|
3
|
+
"version": "1.9.0",
|
|
4
|
+
"description": "AI-assisted development setup kit. Auto-detects your tech stack (Next.js, Sitecore XM Cloud, Optimizely SaaS CMS, Tailwind, TypeScript, Turborepo) and generates tailored CLAUDE.md, .cursorrules, hooks, agents, context modes, slash commands, design token rules, component registries, developer guides, and spec-first workflows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"ai-kit": "./dist/index.js"
|
|
@@ -37,6 +37,13 @@
|
|
|
37
37
|
"nextjs",
|
|
38
38
|
"tailwind",
|
|
39
39
|
"sitecore",
|
|
40
|
+
"sitecore-xm-cloud",
|
|
41
|
+
"sitecore-jss",
|
|
42
|
+
"optimizely",
|
|
43
|
+
"optimizely-saas-cms",
|
|
44
|
+
"optimizely-graph",
|
|
45
|
+
"optimizely-visual-builder",
|
|
46
|
+
"headless-cms",
|
|
40
47
|
"docker",
|
|
41
48
|
"storybook",
|
|
42
49
|
"i18n",
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
# Optimizely SaaS CMS
|
|
2
|
+
|
|
3
|
+
## Architecture
|
|
4
|
+
- This is a headless CMS project using Optimizely SaaS CMS (CMS 13+) with Next.js as the frontend
|
|
5
|
+
- Content is authored in the Optimizely cloud editor and delivered via **Optimizely Graph** (GraphQL API at `https://cg.optimizely.com`)
|
|
6
|
+
- Components are mapped to CMS content types through a **ComponentFactory** pattern
|
|
7
|
+
- The SDK ecosystem is `@remkoj/optimizely-*` (community SDK by Remko Jantzen)
|
|
8
|
+
- Visual Builder enables drag-and-drop page composition with Experiences, Sections, Rows, Columns, and Elements
|
|
9
|
+
|
|
10
|
+
## SDK Packages
|
|
11
|
+
|
|
12
|
+
| Package | Purpose |
|
|
13
|
+
|---|---|
|
|
14
|
+
| `@remkoj/optimizely-cms-react` | Core React components: `<CmsContent>`, `<CmsContentArea>`, `<CmsEditable>`, `<RichText>`, `<OptimizelyComposition>` |
|
|
15
|
+
| `@remkoj/optimizely-cms-nextjs` | Next.js integration: `createPage()`, `createPublishApi()`, `createEditPageComponent()`, middleware |
|
|
16
|
+
| `@remkoj/optimizely-graph-client` | GraphQL client for Optimizely Graph with HMAC/Basic/Token auth |
|
|
17
|
+
| `@remkoj/optimizely-graph-functions` | GraphQL Codegen preset for generating typed queries and fragments |
|
|
18
|
+
| `@remkoj/optimizely-cms-api` | REST API wrapper for CMS Integration API (content CRUD, type management) |
|
|
19
|
+
| `@remkoj/optimizely-cms-cli` | CLI (`opti-cms`) for type sync, component scaffolding, factory generation |
|
|
20
|
+
| `@remkoj/optimizely-graph-cli` | CLI (`opti-graph`) for webhook and source management |
|
|
21
|
+
| `@remkoj/optimizely-one-nextjs` | Browser-side Optimizely products (Web Experimentation, Data Platform, Recommendations) |
|
|
22
|
+
|
|
23
|
+
## Content Type Hierarchy
|
|
24
|
+
|
|
25
|
+
| Type | Description | GraphQL Fragment Suffix |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| **Page** | Routable content with a URL path | `.page.graphql` |
|
|
28
|
+
| **Block/Component** | Reusable content placed in Content Areas | `.block.graphql` or `.component.graphql` |
|
|
29
|
+
| **Element** | Lightweight components for Visual Builder | `.element.graphql` |
|
|
30
|
+
| **Experience** | Visual Builder top-level compositions | `.experience.graphql` |
|
|
31
|
+
| **Section** | Visual Builder layout containers | `.section.graphql` |
|
|
32
|
+
| **Media/Image/Video** | Asset types | N/A |
|
|
33
|
+
|
|
34
|
+
## Component Patterns
|
|
35
|
+
|
|
36
|
+
### Page Component (catch-all route)
|
|
37
|
+
```typescript
|
|
38
|
+
// src/app/[[...path]]/page.tsx
|
|
39
|
+
import { createPage } from '@remkoj/optimizely-cms-nextjs/page'
|
|
40
|
+
import { factory } from '@/components/factory'
|
|
41
|
+
import { createClient } from '@remkoj/optimizely-graph-client'
|
|
42
|
+
import { AuthMode } from '@remkoj/optimizely-graph-client'
|
|
43
|
+
import { draftMode } from 'next/headers'
|
|
44
|
+
|
|
45
|
+
const { CmsPage, generateMetadata, generateStaticParams } = createPage(factory, {
|
|
46
|
+
client(token?: string, mode?: 'request' | 'metadata') {
|
|
47
|
+
const client = createClient(undefined, token, { nextJsFetchDirectives: true })
|
|
48
|
+
if (mode === 'request') {
|
|
49
|
+
const { isEnabled } = draftMode()
|
|
50
|
+
if (isEnabled) {
|
|
51
|
+
client.updateAuthentication(AuthMode.HMAC)
|
|
52
|
+
client.enablePreview()
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return client
|
|
56
|
+
},
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
export const dynamic = 'error'
|
|
60
|
+
export const dynamicParams = true
|
|
61
|
+
export const revalidate = false
|
|
62
|
+
export default CmsPage
|
|
63
|
+
export { generateMetadata, generateStaticParams }
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Component Factory Setup
|
|
67
|
+
```typescript
|
|
68
|
+
// src/components/factory.ts
|
|
69
|
+
import { DefaultComponentFactory, RichTextComponentDictionary } from '@remkoj/optimizely-cms-react/rsc'
|
|
70
|
+
import { CmsFactory } from './cms/index'
|
|
71
|
+
|
|
72
|
+
const factory = new DefaultComponentFactory()
|
|
73
|
+
factory.registerAll(RichTextComponentDictionary)
|
|
74
|
+
factory.registerAll(CmsFactory)
|
|
75
|
+
|
|
76
|
+
export { factory }
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Component Dictionary (auto-generated by `opti-cms nextjs:factory`)
|
|
80
|
+
```typescript
|
|
81
|
+
// src/components/cms/index.ts
|
|
82
|
+
import type { ComponentTypeDictionary } from '@remkoj/optimizely-cms-react'
|
|
83
|
+
|
|
84
|
+
import StandardPage from './page/StandardPage'
|
|
85
|
+
import HeroBlock from './block/HeroBlock'
|
|
86
|
+
import TextElement from './element/TextElement'
|
|
87
|
+
|
|
88
|
+
export const CmsFactory: ComponentTypeDictionary = [
|
|
89
|
+
{ type: ['Page', 'StandardPage'], component: StandardPage },
|
|
90
|
+
{ type: ['Block', 'HeroBlock'], component: HeroBlock },
|
|
91
|
+
{ type: ['Element', 'TextElement'], component: TextElement },
|
|
92
|
+
]
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### CMS Content Component
|
|
96
|
+
```typescript
|
|
97
|
+
// src/components/cms/block/HeroBlock/index.tsx
|
|
98
|
+
import { CmsEditable } from '@remkoj/optimizely-cms-react/rsc'
|
|
99
|
+
import { RichText } from '@remkoj/optimizely-cms-react/rsc'
|
|
100
|
+
|
|
101
|
+
interface HeroBlockProps {
|
|
102
|
+
data: {
|
|
103
|
+
heading?: string
|
|
104
|
+
description?: { html?: string }
|
|
105
|
+
image?: { url?: string; altText?: string }
|
|
106
|
+
ctaText?: string
|
|
107
|
+
ctaLink?: { default?: string }
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export default function HeroBlock({ data }: HeroBlockProps) {
|
|
112
|
+
return (
|
|
113
|
+
<CmsEditable as="section" className="hero-block">
|
|
114
|
+
{data.heading && <h1>{data.heading}</h1>}
|
|
115
|
+
{data.description && <RichText html={data.description.html} />}
|
|
116
|
+
{data.image?.url && (
|
|
117
|
+
<img src={data.image.url} alt={data.image.altText || ''} />
|
|
118
|
+
)}
|
|
119
|
+
{data.ctaText && data.ctaLink?.default && (
|
|
120
|
+
<a href={data.ctaLink.default}>{data.ctaText}</a>
|
|
121
|
+
)}
|
|
122
|
+
</CmsEditable>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### GraphQL Fragment for a Component
|
|
128
|
+
```graphql
|
|
129
|
+
# src/components/cms/block/HeroBlock/HeroBlock.block.graphql
|
|
130
|
+
fragment HeroBlockData on HeroBlock {
|
|
131
|
+
heading
|
|
132
|
+
description {
|
|
133
|
+
html
|
|
134
|
+
}
|
|
135
|
+
image {
|
|
136
|
+
url
|
|
137
|
+
altText
|
|
138
|
+
}
|
|
139
|
+
ctaText
|
|
140
|
+
ctaLink {
|
|
141
|
+
default
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Visual Builder Composition
|
|
147
|
+
|
|
148
|
+
The Visual Builder outputs a **composition tree** that the frontend renders recursively:
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
Experience (top-level)
|
|
152
|
+
-> Section (layout container)
|
|
153
|
+
-> Row
|
|
154
|
+
-> Column
|
|
155
|
+
-> Component (actual content element)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Render compositions using `<OptimizelyComposition>`:
|
|
159
|
+
```typescript
|
|
160
|
+
import { OptimizelyComposition } from '@remkoj/optimizely-cms-react/rsc'
|
|
161
|
+
|
|
162
|
+
// The composition data comes from GraphQL and is rendered automatically
|
|
163
|
+
// Structure nodes (experience, section, row, column) are layout containers
|
|
164
|
+
// Component nodes are resolved through the ComponentFactory
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## GraphQL Codegen Setup
|
|
168
|
+
```typescript
|
|
169
|
+
// codegen.ts
|
|
170
|
+
import type { CodegenConfig } from '@graphql-codegen/cli'
|
|
171
|
+
import getSchemaInfo from '@remkoj/optimizely-graph-client/codegen'
|
|
172
|
+
import OptimizelyGraphPreset from '@remkoj/optimizely-graph-functions/preset'
|
|
173
|
+
|
|
174
|
+
const config: CodegenConfig = {
|
|
175
|
+
schema: getSchemaInfo(),
|
|
176
|
+
documents: ['src/**/*.graphql'],
|
|
177
|
+
generates: {
|
|
178
|
+
'src/gql/': {
|
|
179
|
+
preset: OptimizelyGraphPreset,
|
|
180
|
+
presetConfig: {
|
|
181
|
+
recursion: true,
|
|
182
|
+
gqlTagName: 'gql',
|
|
183
|
+
injections: [
|
|
184
|
+
{ into: 'PageData', pathRegex: 'src/components/cms/.*\\.page\\.graphql' },
|
|
185
|
+
{ into: 'BlockData', pathRegex: 'src/components/cms/.*\\.(block|component|section)\\.graphql' },
|
|
186
|
+
{ into: 'ElementData', pathRegex: 'src/components/cms/.*\\.element\\.graphql' },
|
|
187
|
+
],
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export default config
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Cache Invalidation Webhook
|
|
197
|
+
```typescript
|
|
198
|
+
// src/app/.well-known/publish/route.ts
|
|
199
|
+
import createPublishApi from '@remkoj/optimizely-cms-nextjs/publish'
|
|
200
|
+
|
|
201
|
+
const handler = createPublishApi({ optimizePublish: true })
|
|
202
|
+
export const GET = handler
|
|
203
|
+
export const POST = handler
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Preview / Edit Mode
|
|
207
|
+
```typescript
|
|
208
|
+
// src/app/preview/page.tsx
|
|
209
|
+
import { createEditPageComponent } from '@remkoj/optimizely-cms-nextjs/preview'
|
|
210
|
+
import { factory } from '@/components/factory'
|
|
211
|
+
import { createClient } from '@remkoj/optimizely-graph-client'
|
|
212
|
+
|
|
213
|
+
export default createEditPageComponent(factory, {
|
|
214
|
+
loader: getContentById,
|
|
215
|
+
clientFactory: (token) => createClient(undefined, token, { cache: false }),
|
|
216
|
+
refreshTimeout: 500,
|
|
217
|
+
})
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
// src/app/.well-known/drafts/route.ts
|
|
222
|
+
// Toggles Next.js draft mode for preview in the Optimizely editor
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## CLI Commands
|
|
226
|
+
|
|
227
|
+
### Content Type Management
|
|
228
|
+
```bash
|
|
229
|
+
# Pull content types from CMS to local JSON files
|
|
230
|
+
npx opti-cms types:pull
|
|
231
|
+
|
|
232
|
+
# Push local content type definitions to CMS
|
|
233
|
+
npx opti-cms types:push
|
|
234
|
+
|
|
235
|
+
# Generate component factory from content types
|
|
236
|
+
npx opti-cms nextjs:factory
|
|
237
|
+
|
|
238
|
+
# Scaffold new component files
|
|
239
|
+
npx opti-cms nextjs:create
|
|
240
|
+
|
|
241
|
+
# Generate component implementations from types
|
|
242
|
+
npx opti-cms nextjs:components
|
|
243
|
+
|
|
244
|
+
# Generate GraphQL fragments for components
|
|
245
|
+
npx opti-cms nextjs:fragments
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Optimizely Graph Management
|
|
249
|
+
```bash
|
|
250
|
+
# Manage webhooks for cache invalidation
|
|
251
|
+
npx opti-graph webhooks:list
|
|
252
|
+
npx opti-graph webhooks:create
|
|
253
|
+
|
|
254
|
+
# Manage content sources
|
|
255
|
+
npx opti-graph sources:list
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### GraphQL Codegen
|
|
259
|
+
```bash
|
|
260
|
+
# Generate typed queries and fragments
|
|
261
|
+
npx graphql-codegen
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Content Type Definitions
|
|
265
|
+
|
|
266
|
+
Content types are defined in `.opti-type.json` files:
|
|
267
|
+
```json
|
|
268
|
+
{
|
|
269
|
+
"key": "HeroBlock",
|
|
270
|
+
"displayName": "Hero Block",
|
|
271
|
+
"description": "A hero banner with heading, description, and CTA",
|
|
272
|
+
"baseType": "block",
|
|
273
|
+
"properties": {
|
|
274
|
+
"heading": { "type": "string", "required": true },
|
|
275
|
+
"description": { "type": "richText" },
|
|
276
|
+
"image": { "type": "contentReference" },
|
|
277
|
+
"ctaText": { "type": "string" },
|
|
278
|
+
"ctaLink": { "type": "url" }
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Style definitions use `.opti-style.json` for Visual Builder styling:
|
|
284
|
+
```json
|
|
285
|
+
{
|
|
286
|
+
"key": "HeroBlockStyles",
|
|
287
|
+
"displayName": "Hero Block Styles",
|
|
288
|
+
"target": "HeroBlock",
|
|
289
|
+
"properties": {
|
|
290
|
+
"theme": { "type": "select", "options": ["light", "dark"] },
|
|
291
|
+
"spacing": { "type": "select", "options": ["compact", "normal", "spacious"] }
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## Environment Setup
|
|
297
|
+
|
|
298
|
+
Required `.env.local` variables:
|
|
299
|
+
```
|
|
300
|
+
# Optimizely Graph (content delivery)
|
|
301
|
+
OPTIMIZELY_GRAPH_APP_KEY=your-app-key
|
|
302
|
+
OPTIMIZELY_GRAPH_SECRET=your-secret
|
|
303
|
+
OPTIMIZELY_GRAPH_SINGLE_KEY=your-single-key
|
|
304
|
+
OPTIMIZELY_GRAPH_GATEWAY=https://cg.optimizely.com
|
|
305
|
+
OPTIMIZELY_GRAPH_TENANT_ID=your-tenant-id
|
|
306
|
+
|
|
307
|
+
# Optimizely CMS (management API)
|
|
308
|
+
OPTIMIZELY_CMS_URL=https://your-instance.cms.optimizely.com
|
|
309
|
+
OPTIMIZELY_CMS_CLIENT_ID=your-client-id
|
|
310
|
+
OPTIMIZELY_CMS_CLIENT_SECRET=your-client-secret
|
|
311
|
+
|
|
312
|
+
# Frontend
|
|
313
|
+
SITE_DOMAIN=your-frontend-domain.com
|
|
314
|
+
OPTIMIZELY_PUBLISH_TOKEN=your-webhook-token
|
|
315
|
+
|
|
316
|
+
# Debug (optional)
|
|
317
|
+
OPTIMIZELY_DEBUG=0
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## Project File Structure
|
|
321
|
+
```
|
|
322
|
+
src/
|
|
323
|
+
app/
|
|
324
|
+
[[...path]]/page.tsx # Catch-all CMS page route (createPage)
|
|
325
|
+
preview/page.tsx # Visual editor preview page
|
|
326
|
+
.well-known/
|
|
327
|
+
drafts/route.ts # Draft mode toggle endpoint
|
|
328
|
+
publish/route.ts # Webhook cache invalidation endpoint
|
|
329
|
+
channel/route.ts # Channel info endpoint
|
|
330
|
+
layout.tsx # Root layout
|
|
331
|
+
components/
|
|
332
|
+
factory.ts # ComponentFactory setup
|
|
333
|
+
cms/
|
|
334
|
+
index.ts # Auto-generated component dictionary
|
|
335
|
+
page/ # Page-type components
|
|
336
|
+
StandardPage/
|
|
337
|
+
index.tsx
|
|
338
|
+
StandardPage.page.graphql
|
|
339
|
+
block/ # Block-type components
|
|
340
|
+
HeroBlock/
|
|
341
|
+
index.tsx
|
|
342
|
+
HeroBlock.block.graphql
|
|
343
|
+
element/ # Element-type components (Visual Builder)
|
|
344
|
+
TextElement/
|
|
345
|
+
index.tsx
|
|
346
|
+
TextElement.element.graphql
|
|
347
|
+
experience/ # Experience-type components (Visual Builder)
|
|
348
|
+
section/ # Section-type components (Visual Builder)
|
|
349
|
+
gql/ # Auto-generated by GraphQL Codegen
|
|
350
|
+
functions.ts
|
|
351
|
+
client.ts
|
|
352
|
+
types.ts
|
|
353
|
+
channel.ts # Channel/site definition
|
|
354
|
+
codegen.ts # GraphQL Codegen configuration
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
## Rules
|
|
358
|
+
- Always use `<CmsEditable>` wrapper for components that should be editable in the Optimizely editor
|
|
359
|
+
- Use `<RichText>` from `@remkoj/optimizely-cms-react/rsc` for rich text fields — never render raw HTML with `dangerouslySetInnerHTML`
|
|
360
|
+
- GraphQL fragments MUST follow the naming convention: `[Name].[type].graphql` (e.g., `HeroBlock.block.graphql`)
|
|
361
|
+
- Fragment file suffix determines injection target: `.page.graphql` -> PageData, `.block.graphql` -> BlockData, `.element.graphql` -> ElementData
|
|
362
|
+
- Always register new components in the ComponentFactory dictionary — use `opti-cms nextjs:factory` to regenerate
|
|
363
|
+
- Content type arrays use `[category, typeName]` format (e.g., `['Block', 'HeroBlock']`) — match the CMS type definition exactly
|
|
364
|
+
- Use `createPage()` from `@remkoj/optimizely-cms-nextjs/page` for the catch-all route — don't build custom routing
|
|
365
|
+
- Use `createPublishApi()` for the webhook endpoint — don't build custom cache invalidation
|
|
366
|
+
- Handle draft mode properly: check `draftMode().isEnabled` and switch to HMAC auth with `client.enablePreview()`
|
|
367
|
+
- Run `npx graphql-codegen` after changing `.graphql` files to regenerate typed queries
|
|
368
|
+
- Use `opti-cms types:pull` to sync content types from CMS to local JSON — don't manually create `.opti-type.json` files from scratch
|
|
369
|
+
- For Visual Builder components, follow the composition tree hierarchy: Experience > Section > Row > Column > Element
|
|
370
|
+
- Import RSC (React Server Component) versions from `@remkoj/optimizely-cms-react/rsc` — not from the base package
|
|
371
|
+
- Never hardcode content that should come from CMS content types
|
|
372
|
+
- Keep `codegen.ts` injection patterns in sync with your component folder structure
|
|
373
|
+
|
|
374
|
+
## Common Mistakes to Avoid
|
|
375
|
+
- Don't render rich text with `dangerouslySetInnerHTML` — use `<RichText html={data.field.html} />`
|
|
376
|
+
- Don't forget to wrap editable components with `<CmsEditable>` — the editor won't recognize them without it
|
|
377
|
+
- Don't create GraphQL fragments with wrong file suffixes — `.block.graphql` and `.page.graphql` are injected into different parent fragments
|
|
378
|
+
- Don't manually edit `src/components/cms/index.ts` — it's auto-generated by `opti-cms nextjs:factory`
|
|
379
|
+
- Don't use `getStaticProps` or `getServerSideProps` — use `createPage()` which handles SSG/SSR/ISR automatically
|
|
380
|
+
- Don't forget to run `npx graphql-codegen` after adding/modifying GraphQL fragments
|
|
381
|
+
- Don't mix RSC and client component imports — use `/rsc` sub-path for Server Components
|
|
382
|
+
- Don't skip the `.well-known/publish` webhook route — without it, content updates won't invalidate the cache
|
|
383
|
+
- Don't hardcode the Graph gateway URL — use environment variables and let the client auto-configure
|
|
384
|
+
- Don't forget HMAC auth for preview mode — public auth only returns published content
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Optimizely SaaS CMS Rules
|
|
2
|
+
|
|
3
|
+
- This is a headless CMS project using **Optimizely SaaS CMS** with Next.js, delivered via **Optimizely Graph** (GraphQL)
|
|
4
|
+
- SDK packages: `@remkoj/optimizely-cms-react`, `@remkoj/optimizely-cms-nextjs`, `@remkoj/optimizely-graph-client`
|
|
5
|
+
- Use `<CmsEditable>` wrapper for all editable components — the editor won't recognize them without it
|
|
6
|
+
- Use `<RichText html={data.field.html} />` from `@remkoj/optimizely-cms-react/rsc` — never use `dangerouslySetInnerHTML`
|
|
7
|
+
- Import Server Component versions from `@remkoj/optimizely-cms-react/rsc` — not from the base package
|
|
8
|
+
- GraphQL fragment naming: `[Name].page.graphql`, `[Name].block.graphql`, `[Name].element.graphql` — suffix determines injection target
|
|
9
|
+
- Register components in ComponentFactory with `[category, typeName]` format (e.g., `['Block', 'HeroBlock']`)
|
|
10
|
+
- Use `createPage()` from `@remkoj/optimizely-cms-nextjs/page` for the catch-all `[[...path]]/page.tsx` route
|
|
11
|
+
- Use `createPublishApi()` for cache invalidation webhooks at `.well-known/publish/route.ts`
|
|
12
|
+
- Handle draft mode: check `draftMode().isEnabled`, switch to HMAC auth, call `client.enablePreview()`
|
|
13
|
+
- Run `npx graphql-codegen` after modifying `.graphql` files to regenerate typed queries
|
|
14
|
+
- Use `opti-cms nextjs:factory` to regenerate the component dictionary — don't manually edit the auto-generated index
|
|
15
|
+
- Content type definitions use `.opti-type.json` files — sync with `opti-cms types:pull` / `types:push`
|
|
16
|
+
- Visual Builder hierarchy: Experience > Section > Row > Column > Element — render with `<OptimizelyComposition>`
|
|
17
|
+
- Don't hardcode content that should come from CMS content types
|
|
18
|
+
- Don't use `getStaticProps`/`getServerSideProps` — `createPage()` handles SSG/SSR/ISR automatically
|
|
19
|
+
- Keep `codegen.ts` injection patterns in sync with component folder structure
|
|
20
|
+
- Environment: `OPTIMIZELY_GRAPH_SINGLE_KEY` (public), `OPTIMIZELY_GRAPH_APP_KEY` + `SECRET` (HMAC auth)
|