@se-studio/project-build 1.0.131 → 1.0.133

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +13 -14
  3. package/package.json +2 -4
  4. package/dist/management/sync-skills.d.ts +0 -4
  5. package/dist/management/sync-skills.d.ts.map +0 -1
  6. package/dist/management/sync-skills.js +0 -97
  7. package/dist/management/sync-skills.js.map +0 -1
  8. package/dist/seskills.d.ts +0 -9
  9. package/dist/seskills.d.ts.map +0 -1
  10. package/dist/seskills.js +0 -32
  11. package/dist/seskills.js.map +0 -1
  12. package/skills/contentful-cms/alt-text-audit/SKILL.md +0 -60
  13. package/skills/contentful-cms/cms-guidelines/README.md +0 -166
  14. package/skills/contentful-cms/cms-guidelines/colour-hint-prompt.md +0 -77
  15. package/skills/contentful-cms/cms-guidelines/evaluation-prompt.md +0 -84
  16. package/skills/contentful-cms/cms-guidelines/generate-component-guidelines.md +0 -126
  17. package/skills/contentful-cms/cms-guidelines/generation-prompt.md +0 -231
  18. package/skills/contentful-cms/cms-guidelines/html-component-authoring.md +0 -401
  19. package/skills/contentful-cms/cms-guidelines/validation-prompt.md +0 -170
  20. package/skills/contentful-cms/cms-guidelines/variant-loop.md +0 -189
  21. package/skills/contentful-cms/cms-guidelines/variant-proposal-prompt.md +0 -131
  22. package/skills/contentful-cms/core/SKILL.md +0 -793
  23. package/skills/contentful-cms/generate-all-guidelines/SKILL.md +0 -313
  24. package/skills/contentful-cms/generate-cms-guidelines/SKILL.md +0 -313
  25. package/skills/contentful-cms/image-guide/SKILL.md +0 -240
  26. package/skills/contentful-cms/manifest.json +0 -11
  27. package/skills/contentful-cms/navigation/SKILL.md +0 -23
  28. package/skills/contentful-cms/rich-text/SKILL.md +0 -96
  29. package/skills/contentful-cms/schema-org/SKILL.md +0 -74
  30. package/skills/contentful-cms/screenshots/SKILL.md +0 -46
  31. package/skills/contentful-cms/seo-descriptions/SKILL.md +0 -54
  32. package/skills/contentful-cms/templates/SKILL.md +0 -21
  33. package/skills/contentful-cms/update-cms-guidelines/SKILL.md +0 -348
  34. package/skills/se-marketing-sites/cms-routes-and-appshared/SKILL.md +0 -99
  35. package/skills/se-marketing-sites/create-collection/SKILL.md +0 -295
  36. package/skills/se-marketing-sites/create-component/SKILL.md +0 -250
  37. package/skills/se-marketing-sites/create-page/SKILL.md +0 -129
  38. package/skills/se-marketing-sites/curate-showcase-mocks/SKILL.md +0 -343
  39. package/skills/se-marketing-sites/handling-media/SKILL.md +0 -195
  40. package/skills/se-marketing-sites/lib-cms-structure/SKILL.md +0 -83
  41. package/skills/se-marketing-sites/register-cms-features/SKILL.md +0 -95
  42. package/skills/se-marketing-sites/styling-system/SKILL.md +0 -122
@@ -1,295 +0,0 @@
1
- ---
2
- name: create-cms-collection
3
- description: Guide for creating new CMS-driven collections (component containers) in the SE Core Product monorepo. Use this skill when asked to create a new collection or "grid" component.
4
- license: Private
5
- metadata:
6
- author: se-core-product
7
- version: "1.1.0"
8
- ---
9
-
10
- # Creating CMS Collections
11
-
12
- Collections are components that contain other components (usually referred to as "Cards" in this context). They follow the same **four-layer architecture** as standard components but use specialized helpers.
13
-
14
- ## Architecture Overview
15
-
16
- Like components, collections **MUST be Server Components** by default. Complex interactions (carousels, filters) must be isolated.
17
-
18
- 1. **Layer 1: Core Collection**
19
- * Named export: `Core[CollectionName]`
20
- * Iterates over `contents` array
21
- * Renders children (Cards)
22
- * Handles layout (grid, list) - **Server-side only**
23
- * If a Carousel or Filter is needed, wraps children in a Client Component.
24
-
25
- 2. **Layer 2: Wrapper Collection**
26
- * Named export: `[CollectionName]`
27
- * Wraps Core with `<Section>`
28
- * Handles outer spacing & background
29
-
30
- 3. **Layer 3: Renderer**
31
- * Default export: `[CollectionName]`
32
- * Implements `CollectionRenderer`
33
- * Wraps with `UnusedChecker`
34
-
35
- 4. **Layer 4: Registration**
36
- * Named export: `[CollectionName]Registration`
37
- * Uses `defineCollection` (NOT `defineComponent`)
38
- * Defines `usedFields` for the collection itself
39
- * Defines `cardUsedFields` for the children components
40
-
41
- ## Client Component Strategy
42
-
43
- **Rule: Isolate Client Logic (Carousels, Animations, Filters).**
44
-
45
- Never make the whole collection a Client Component.
46
-
47
- * **BAD**: `'use client'` on `MyCollection.tsx`.
48
- * **GOOD**: Create `MyCollectionClient.tsx` (e.g., `<CarouselClient>`) that accepts children.
49
-
50
- ### Pattern: The Client Wrapper
51
-
52
- For a carousel:
53
- 1. **Server Component** (`CoreMyCollection`): Renders the loop of cards.
54
- 2. **Client Component** (`CarouselClient`): Imports the carousel library (swiper, embla), manages state.
55
- 3. **Usage**: Pass the server-rendered cards as `children` to the client wrapper.
56
-
57
- ```tsx
58
- // CoreMyCollection.tsx (Server)
59
- <CarouselClient>
60
- {contents.map(item => <Card content={item} />)}
61
- </CarouselClient>
62
-
63
- // CarouselClient.tsx (Client)
64
- 'use client';
65
- export const CarouselClient = ({ children }) => {
66
- return <Slider>{children}</Slider>; // Client logic here
67
- }
68
- ```
69
-
70
- This ensures the card content is SEO-ready and server-rendered, while only the slider mechanism is client-side.
71
-
72
- ## Quick Start Template
73
-
74
- Use this template for new collections. Replace `MyCollection` with your collection name.
75
-
76
- ```typescript
77
- import type { IContentContext } from '@se-studio/core-data-types';
78
- import { isComponent } from '@se-studio/core-data-types';
79
- import {
80
- type CmsRendererConfig,
81
- type CollectionRenderer,
82
- cn,
83
- defineCollection,
84
- getPreviewFieldProps,
85
- RtfOrString,
86
- UnusedChecker,
87
- } from '@se-studio/core-ui';
88
- import { SectionLinks } from '@/elements/SectionLinks';
89
- import { Section } from '@/framework/Section';
90
- import type { ICollection, IComponent, IContent } from '@/lib/cms';
91
- import { getSizingInformation } from '@/lib/SizingInformation';
92
- // import MyCollectionClient from './MyCollectionClient'; // If needed
93
-
94
- // 1. Define fields for the Collection itself
95
- const FIELD_KEYS = [
96
- 'id',
97
- 'index',
98
- 'heading',
99
- 'body',
100
- 'contents', // The children
101
- 'links',
102
- 'anchor',
103
- 'cmsLabel',
104
- ] as const;
105
-
106
- type Fields = Pick<ICollection, (typeof FIELD_KEYS)[number]>;
107
- const USED_FIELDS = new Set([...FIELD_KEYS, 'collectionType']);
108
-
109
- // 2. Define fields used by the Child Cards
110
- // (If the children are generic components, this might just be 'id' and 'componentType')
111
- const CARD_FIELD_KEYS = [
112
- 'id',
113
- 'heading',
114
- 'body',
115
- 'visual',
116
- 'links',
117
- 'cmsLabel',
118
- ] as const;
119
- const CARD_USED_FIELDS = new Set([...CARD_FIELD_KEYS, 'componentType']);
120
-
121
- // Helper to render individual cards (use ChildElement for card titles – items inside a collection)
122
- const CardWrapper: React.FC<{
123
- content: IComponent;
124
- index: number;
125
- contentContext: IContentContext;
126
- rendererConfig: CmsRendererConfig;
127
- }> = ({ content, index, contentContext, rendererConfig }) => {
128
- const { ChildElement, sizingInformation } = getSizingInformation(1);
129
- return (
130
- <div className="p-6 bg-white rounded-lg shadow-sm">
131
- {content.heading && (
132
- <ChildElement className={cn(sizingInformation.h3, 'mb-2')}>
133
- {content.heading}
134
- </ChildElement>
135
- )}
136
- {/* ... */}
137
- </div>
138
- );
139
- };
140
-
141
- // 3. Layer 1: Core Collection (Server Component)
142
- const CoreMyCollection: React.FC<
143
- Fields & {
144
- contentContext: IContentContext;
145
- rendererConfig: CmsRendererConfig;
146
- positionClassName?: string;
147
- }
148
- > = ({
149
- id,
150
- index,
151
- heading,
152
- body,
153
- contents,
154
- links,
155
- cmsLabel,
156
- contentContext,
157
- rendererConfig,
158
- positionClassName,
159
- }) => {
160
- const { Element, sizingInformation } = getSizingInformation(index);
161
-
162
- return (
163
- <div className={cn('space-y-8', positionClassName)}>
164
- <div className="text-center space-y-4">
165
- {heading && (
166
- <Element
167
- {...getPreviewFieldProps(rendererConfig, id, 'heading')}
168
- className={cn(sizingInformation.h2)}
169
- >
170
- {heading}
171
- </Element>
172
- )}
173
- {body && (
174
- <RtfOrString
175
- {...getPreviewFieldProps(rendererConfig, id, 'body')}
176
- content={body}
177
- className="rtf-standard"
178
- rendererConfig={rendererConfig}
179
- contentContext={contentContext}
180
- />
181
- )}
182
- </div>
183
-
184
- {contents && contents.length > 0 && (
185
- <ul className="grid grid-cols-1 laptop:grid-cols-3 gap-6">
186
- {contents.filter(isComponent).map((item, i) => (
187
- <li key={item.id}>
188
- <CardWrapper
189
- content={item}
190
- index={i}
191
- contentContext={contentContext}
192
- rendererConfig={rendererConfig}
193
- />
194
- </li>
195
- ))}
196
- </ul>
197
- )}
198
-
199
- {links && (
200
- <SectionLinks
201
- links={links}
202
- rendererConfig={rendererConfig}
203
- trackingLocation={cmsLabel}
204
- analyticsContext={contentContext.analyticsContext}
205
- className="justify-center"
206
- />
207
- )}
208
- </div>
209
- );
210
- };
211
-
212
- // 4. Layer 2: Wrapper Collection
213
- const MyCollection: React.FC<
214
- Fields & {
215
- information: IContent;
216
- contentContext: IContentContext;
217
- rendererConfig: CmsRendererConfig;
218
- }
219
- > = ({ information, ...rest }) => {
220
- return (
221
- <Section
222
- componentName={information.collectionType}
223
- information={information}
224
- previewHelpers={rest.rendererConfig?.previewHelpers}
225
- >
226
- <CoreMyCollection
227
- {...rest}
228
- positionClassName="col-span-full"
229
- />
230
- </Section>
231
- );
232
- };
233
-
234
- // 5. Layer 3: Renderer
235
- const MyCollectionRenderer: CollectionRenderer<ICollection> = ({
236
- information,
237
- contentContext,
238
- rendererConfig,
239
- }) => {
240
- const { collectionType, ...rest } = information;
241
-
242
- return (
243
- <UnusedChecker
244
- componentName={collectionType}
245
- allFields={rest}
246
- usedFields={USED_FIELDS}
247
- >
248
- <MyCollection
249
- information={information}
250
- contentContext={contentContext}
251
- rendererConfig={rendererConfig}
252
- {...(rest as Fields)}
253
- />
254
- </UnusedChecker>
255
- );
256
- };
257
-
258
- export default MyCollectionRenderer;
259
-
260
- // 6. Layer 4: Registration
261
- export const MyCollectionRegistration = defineCollection({
262
- name: 'My Collection', // Must match CMS collectionType
263
- renderer: MyCollectionRenderer,
264
- usedFields: USED_FIELDS,
265
- cardUsedFields: CARD_USED_FIELDS,
266
- mock: {
267
- heading: 'Sample Collection',
268
- body: 'Collection description text',
269
- },
270
- });
271
- ```
272
-
273
- ## Checklist for Collections
274
-
275
- 1. **Define Two Sets of Fields**:
276
- * `USED_FIELDS` for the collection container (heading, body, background).
277
- * `CARD_USED_FIELDS` for the items inside `contents`.
278
- 2. **Use `defineCollection`**:
279
- * Instead of `defineComponent`.
280
- * Pass both `usedFields` and `cardUsedFields`.
281
- 3. **Heading hierarchy**:
282
- * Use **Element** from `getSizingInformation(index)` for the collection's section heading (and a class from `sizingInformation`). Use **ChildElement** from `getSizingInformation(1)` for item/card titles. Render preHeading/postHeading as **&lt;p&gt;** with a typography class, never as heading tags.
283
- 4. **Preview helpers**:
284
- * Use `getPreviewFieldProps(rendererConfig, id, fieldId)` from `@se-studio/core-ui` for CMS preview (do not import from cms-server – avoids circular dependency). For responsive visuals use `getPreviewResponsiveVisualFieldProps(rendererConfig, id, 'visual', 'mobileVisual')`.
285
- * For collections that fetch data: use `rendererConfig?.fetchHelpers?.getRelatedArticles?.(information, contentContext, count)` instead of importing from cms-server.
286
- 5. **Handle `contents`**:
287
- * Check `if (contents && contents.length > 0)`.
288
- * Filter with `contents.filter(isComponent)`.
289
- * Map over the result to render items.
290
- 6. **Isolate Client Logic**:
291
- * **ENSURE SERVER COMPONENT** for the main file.
292
- * If a carousel/slider/filter is needed, move that logic to `[Name]Client.tsx`.
293
- * Pass server-rendered cards as `children` to the client wrapper.
294
- 7. **Register**:
295
- * Add to `collectionRegistrationsList` in `src/lib/registrations.ts`. The `name` is defined only in your `defineCollection(...)` call.
@@ -1,250 +0,0 @@
1
- ---
2
- name: create-cms-component
3
- description: Guide for creating new CMS-driven components in the SE Core Product monorepo. Use this skill when asked to create a new component, ensuring adherence to the 4-layer architecture.
4
- license: Private
5
- metadata:
6
- author: se-core-product
7
- version: "1.1.0"
8
- ---
9
-
10
- # Creating CMS Components
11
-
12
- This guide explains how to build new CMS-driven components following the established architecture patterns in this codebase.
13
-
14
- ## Architecture Overview
15
-
16
- All components follow a **four-layer architecture** and must remain **Server Components** by default.
17
-
18
- 1. **Layer 1: Core Component (Pure Presentation)**
19
- * Named export: `Core[ComponentName]`
20
- * CMS-agnostic rendering logic
21
- * Accepts `positionClassName` prop
22
- * **MUST be a Server Component** (no `'use client'`)
23
- * If interactivity is needed, import a Client Component here.
24
-
25
- 2. **Layer 2: Wrapper Component (Section Integration)**
26
- * Named export: `[ComponentName]`
27
- * Wraps Core with `<Section>`
28
- * Handles spacing & background
29
-
30
- 3. **Layer 3: Renderer (CMS Integration)**
31
- * Default export: `[ComponentName]` (short name)
32
- * Implements `ComponentRenderer`
33
- * Wraps with `UnusedChecker` for CMS validation
34
-
35
- 4. **Layer 4: Registration (Showcase & CMS)**
36
- * Named export: `[ComponentName]Registration`
37
- * Uses `defineComponent` helper
38
- * Provides metadata & mock data
39
-
40
- ## Client Component Strategy
41
-
42
- **Rule: Isolate Client Logic.**
43
-
44
- Never make the entire component a Client Component just for a small interactive part.
45
-
46
- * **BAD**: Adding `'use client'` to `MyComponent.tsx` (Layers 1-3).
47
- * **GOOD**: Creating `MyComponentClient.tsx` (or `*Animator.tsx`) with `'use client'` and importing it into `CoreMyComponent`.
48
-
49
- ### Why?
50
- * **Performance**: Keeps the majority of the HTML generation on the server.
51
- * **SEO**: Ensures content is fully rendered for crawlers.
52
- * **Bundle Size**: Only sends necessary JS to the browser.
53
-
54
- ### Pattern: The Client Wrapper
55
-
56
- If you need state (e.g., filters, toggles) or effects (e.g., animations), create a wrapper:
57
-
58
- 1. Create `MyComponentClient.tsx` (or `MyComponentAnimator.tsx`).
59
- 2. Add `'use client'` at the top.
60
- 3. Accept `children` or raw data props.
61
- 4. Import and use it in `CoreMyComponent`.
62
-
63
- ## Quick Start Template
64
-
65
- Use this template for new components. Replace `MyNewComponent` with your component name.
66
-
67
- ```typescript
68
- import type { IContentContext } from '@se-studio/core-data-types';
69
- import {
70
- type CmsRendererConfig,
71
- type ComponentRenderer,
72
- convertText,
73
- cn,
74
- defineComponent,
75
- getPreviewFieldProps,
76
- RtfOrString,
77
- UnusedChecker,
78
- } from '@se-studio/core-ui';
79
- import { SectionLinks } from '@/elements/SectionLinks';
80
- import { Section } from '@/framework/Section';
81
- import type { IComponent, IContent } from '@/lib/cms';
82
- import { getSizingInformation } from '@/lib/SizingInformation';
83
- // If interactivity is needed:
84
- // import MyNewComponentClient from './MyNewComponentClient';
85
-
86
- // 1. Define which CMS fields this component uses
87
- const FIELD_KEYS = [
88
- 'id',
89
- 'index',
90
- 'heading',
91
- 'body',
92
- 'links',
93
- 'anchor',
94
- 'cmsLabel',
95
- ] as const;
96
-
97
- type Fields = Pick<IComponent, (typeof FIELD_KEYS)[number]>;
98
-
99
- const USED_FIELDS = new Set([...FIELD_KEYS, 'componentType']);
100
-
101
- // 2. Layer 1: Core Component (Pure presentation, Server Component)
102
- export const CoreMyNewComponent: React.FC<
103
- Fields & {
104
- contentContext?: IContentContext;
105
- rendererConfig?: CmsRendererConfig;
106
- embedded?: boolean;
107
- positionClassName?: string;
108
- }
109
- > = ({
110
- id,
111
- index,
112
- heading,
113
- body,
114
- links,
115
- cmsLabel,
116
- contentContext,
117
- rendererConfig,
118
- positionClassName,
119
- }) => {
120
- const { Element, sizingInformation } = getSizingInformation(index);
121
-
122
- return (
123
- <div className={cn('space-y-6', positionClassName)}>
124
- {heading && (
125
- <Element
126
- {...getPreviewFieldProps(rendererConfig, id, 'heading')}
127
- className={cn(sizingInformation.h1)}
128
- >
129
- {convertText(heading)}
130
- </Element>
131
- )}
132
- {body && (
133
- <RtfOrString
134
- {...getPreviewFieldProps(rendererConfig, id, 'body')}
135
- content={body}
136
- className={cn('rtf-standard')}
137
- rendererConfig={rendererConfig}
138
- contentContext={contentContext}
139
- />
140
- )}
141
- {/* Example of isolated client logic usage: */}
142
- {/* <MyNewComponentClient>
143
- <InteractivePart />
144
- </MyNewComponentClient>
145
- */}
146
- {links && (
147
- <SectionLinks
148
- links={links}
149
- rendererConfig={rendererConfig}
150
- trackingLocation={cmsLabel}
151
- analyticsContext={contentContext?.analyticsContext}
152
- />
153
- )}
154
- </div>
155
- );
156
- };
157
-
158
- // 3. Layer 2: Wrapper Component (Section integration)
159
- const MyNewComponent: React.FC<
160
- Fields &
161
- Pick<IComponent, 'componentType'> & {
162
- contentContext: IContentContext;
163
- information: IContent;
164
- rendererConfig: CmsRendererConfig;
165
- }
166
- > = ({ information, componentType, ...rest }) => {
167
- return (
168
- <Section
169
- componentName={componentType}
170
- information={information}
171
- previewHelpers={rest.rendererConfig?.previewHelpers}
172
- >
173
- <CoreMyNewComponent
174
- {...rest}
175
- positionClassName="col-span-full laptop:col-start-2 laptop:col-span-10"
176
- />
177
- </Section>
178
- );
179
- };
180
-
181
- // 4. Layer 3: Renderer Export (CMS integration)
182
- const MyNew: ComponentRenderer<IComponent> = ({
183
- information,
184
- contentContext,
185
- rendererConfig,
186
- }) => {
187
- const { componentType, ...rest } = information;
188
-
189
- return (
190
- <UnusedChecker componentName={componentType} allFields={rest} usedFields={USED_FIELDS}>
191
- <MyNewComponent
192
- rendererConfig={rendererConfig}
193
- contentContext={contentContext}
194
- information={information}
195
- componentType={componentType}
196
- {...rest}
197
- />
198
- </UnusedChecker>
199
- );
200
- };
201
-
202
- export default MyNew;
203
-
204
- // 5. Layer 4: Registration
205
- export const MyNewRegistration = defineComponent({
206
- name: 'My New Component', // Must match CMS componentType
207
- renderer: MyNew,
208
- usedFields: USED_FIELDS,
209
- mock: {
210
- heading: 'Sample Heading',
211
- body: 'This is sample body text for the showcase.',
212
- },
213
- });
214
- ```
215
-
216
- ## Implementation Checklist
217
-
218
- 1. **Create file**: `src/project/components/MyNewComponent.tsx`
219
- 2. **Define Fields**:
220
- * Set `FIELD_KEYS` with all used fields.
221
- * Create `Fields` type.
222
- * Create `USED_FIELDS` set (don't forget `'componentType'`).
223
- * Include standard fields: `id`, `index`, `anchor`, `cmsLabel`.
224
- 3. **Build Core Component**:
225
- * **ENSURE SERVER COMPONENT**: Do not use `'use client'`.
226
- * Export named `Core[Name]`.
227
- * Accept `positionClassName`.
228
- * Use `getSizingInformation(index)` for semantic headings.
229
- * Check field existence (`field &&`).
230
- * Use `convertText()` for strings.
231
- * Use `<RtfOrString>` for rich text.
232
- * Use `getPreviewFieldProps(rendererConfig, id, fieldId)` from `@se-studio/core-ui` for CMS preview (do not import from cms-server – avoids circular dependency). For responsive visuals use `getPreviewResponsiveVisualFieldProps(rendererConfig, id, 'visual', 'mobileVisual')`.
233
- 4. **Handle Interactivity (If Needed)**:
234
- * Create separate `[Name]Client.tsx` or `[Name]Animator.tsx`.
235
- * Add `'use client'` to that file only.
236
- * Import and use in Core component.
237
- 5. **Build Wrapper Component**:
238
- * Wrap Core with `<Section>`.
239
- * Pass `componentName={componentType}` and `information`.
240
- * Set layout (e.g., `positionClassName="col-span-full"`).
241
- 6. **Build Renderer**:
242
- * Default export (short name).
243
- * Wrap with `<UnusedChecker>`.
244
- 7. **Define Registration**:
245
- * Export named `[Name]Registration`.
246
- * Use `defineComponent`.
247
- * **CRITICAL**: `name` must match CMS `componentType` exactly.
248
- * Provide mock data for showcase.
249
- 8. **Register Component**:
250
- * Add to `componentRegistrationsList` in `src/lib/registrations.ts`. The `name` is defined only in your `defineComponent(...)` call; do not duplicate it as a Record key.
@@ -1,129 +0,0 @@
1
- ---
2
- name: create-page-route
3
- description: Guide for creating new Next.js App Router pages and dynamic routes in the SE Core Product framework. Use when creating CMS-driven pages, adding route segments, or setting up routing.
4
- license: Private
5
- metadata:
6
- author: se-core-product
7
- version: "2.0.0"
8
- ---
9
-
10
- # Creating Pages and Routes
11
-
12
- This guide explains how to create new pages and handle routing in the SE Core Product Next.js framework. The system uses Next.js App Router with Contentful-driven dynamic routing.
13
-
14
- Reference apps: **example-se2026**, **example-brightline**, **example-om1**.
15
-
16
- ## Page Architecture
17
-
18
- ### 1. (cms-routes) Route Group
19
-
20
- All CMS-driven pages live under `src/app/(cms-routes)/`. The layout enables dynamic params:
21
-
22
- ```tsx
23
- // layout.tsx
24
- export const dynamicParams = true;
25
-
26
- export default function CmsRoutesLayout({ children }: { children: React.ReactNode }) {
27
- return children;
28
- }
29
- ```
30
-
31
- ### 2. Route Structure
32
-
33
- | Path | File | Purpose |
34
- |------|------|---------|
35
- | `/` | `page.ts` | Home (slug: `index`) |
36
- | `/{slug}/` | `[level1]/page.ts` | Single-level page |
37
- | `/{slug1}/{slug2}/...` | `[level1]/[...slugs]/page.ts` | Multi-level pages |
38
- | `/articles/` | `articles/page.ts` | Articles index (uses ARTICLES_BASE from constants) |
39
- | `/articles/{type}/` | `articles/[articleType]/page.ts` | Article type index |
40
- | `/articles/{type}/{tag}/` | `articles/[articleType]/[tag]/page.ts` | Article type + tag index |
41
- | `/articles/{type}/{tag}/{article}/` | `articles/[articleType]/[tag]/[...slugs]/page.ts` | Individual article |
42
- | `/tags/`, `/tags/{tag}/` | `tags/` | Tag index (if enabled) |
43
- | `/people/`, `/people/{person}/` | `people/` | People (if enabled) |
44
-
45
- Base paths (e.g. `/articles` vs `/learning-hub`) come from `@/lib/constants`.
46
-
47
- ### 3. appShared Pattern
48
-
49
- Route files are thin wrappers. Data-fetching and rendering logic lives in `src/appShared/`:
50
-
51
- - `pageShared.tsx` – `generatePage`, `generatePageMetadata`
52
- - `articleShared.tsx` – articles
53
- - `articleTypeShared.tsx`, `articleTypesIndexShared.tsx`, `articleTypeTagShared.tsx` – article indices
54
- - `customTypeShared.tsx` – custom types
55
- - `personShared.tsx`, `tagShared.tsx`, `tagsIndexShared.tsx`, `teamIndexShared.tsx`
56
-
57
- ## Route Page Template
58
-
59
- Each route page extracts params and delegates to the appropriate appShared function:
60
-
61
- ```typescript
62
- import type { ResolvingMetadata } from 'next';
63
- import { generatePage, generatePageMetadata } from '@/appShared/pageShared';
64
-
65
- export function generateStaticParams() {
66
- return [];
67
- }
68
-
69
- async function extractDetails(props: PageProps<'/[level1]'>) {
70
- const params = await props.params;
71
- const { level1 } = params;
72
- return { slug: level1, path: `/${level1}/` };
73
- }
74
-
75
- export async function generateMetadata(props: PageProps<'/[level1]'>, parent: ResolvingMetadata) {
76
- const { slug, path } = await extractDetails(props);
77
- return generatePageMetadata(slug, path, true, parent);
78
- }
79
-
80
- export default async function (props: PageProps<'/[level1]'>) {
81
- const { slug, path } = await extractDetails(props);
82
- return generatePage(slug, path, true);
83
- }
84
- ```
85
-
86
- For the home page (`/`), use slug `'index'` and path `'/'` directly.
87
-
88
- ## CmsContent Usage
89
-
90
- The appShared modules render content with:
91
-
92
- ```tsx
93
- <CmsContent
94
- contents={page.contents}
95
- rendererConfig={projectRendererConfig}
96
- pageContext={{ page }}
97
- />
98
- ```
99
-
100
- - `contents` – array of page/article contents (not the full page object)
101
- - `rendererConfig` – from `@/lib/cms-server`
102
- - `pageContext` – provides page/article/customType to child components
103
-
104
- For route files that only need `converterContext` or `buildInformation` (e.g. `generateStaticParams`, sitemap, path computation), import from `@/lib/converter-context` when that module exists, so you don't pull in cms-server.
105
-
106
- ## Creating a Specialized Route
107
-
108
- For a route that behaves differently from standard CMS pages (e.g. search, dashboard):
109
-
110
- 1. Create the route under `(cms-routes)` or a separate route group.
111
- 2. Either create a new appShared module (if the pattern is reusable) or implement inline.
112
- 3. Use `getPageWithErrors`, `getArticleWithErrors`, etc. from `@/lib/cms-server`.
113
- 4. Use `projectRendererConfig` from `@/lib/cms-server`.
114
- 5. Render with `CmsContent` using `contents`, `rendererConfig`, and `pageContext`.
115
-
116
- ## Custom Layouts
117
-
118
- If a route needs a different layout (e.g. no header/footer), create `layout.tsx` in that route directory.
119
-
120
- ## Troubleshooting
121
-
122
- - **404 on new route**: Re-run the dev server or build after adding a dynamic route segment.
123
- - **Static params missing**: Dynamic routes use `generateStaticParams() { return []; }` for on-demand rendering.
124
- - **Preview mode**: Handled by `draftOnly` in `@/lib/server-config`; appShared modules use it automatically.
125
-
126
- ## See Also
127
-
128
- - **cms-routes-and-appshared** – Full route structure and appShared pattern
129
- - **lib-cms-structure** – lib directory layout and cms-server usage