cms-renderer 0.6.3 → 0.6.5
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 +203 -293
- package/dist/lib/block-renderer.js +2 -1
- package/dist/lib/block-renderer.js.map +1 -1
- package/dist/lib/block-toolbar.js +64 -74
- package/dist/lib/block-toolbar.js.map +1 -1
- package/dist/lib/client-editable-block.js +96 -81
- package/dist/lib/client-editable-block.js.map +1 -1
- package/dist/lib/custom-schemas.js.map +1 -1
- package/dist/lib/docs-markdown.d.ts +17 -0
- package/dist/lib/docs-markdown.js +258 -0
- package/dist/lib/docs-markdown.js.map +1 -0
- package/dist/lib/renderer.js +6 -3
- package/dist/lib/renderer.js.map +1 -1
- package/dist/lib/schema.d.ts +8 -0
- package/dist/lib/schema.js +8 -2
- package/dist/lib/schema.js.map +1 -1
- package/dist/lib/types.d.ts +4 -1
- package/dist/lib/types.js +5 -0
- package/dist/lib/types.js.map +1 -1
- package/package.json +16 -5
- package/styles/docs-markdown.css +181 -0
package/README.md
CHANGED
|
@@ -1,385 +1,295 @@
|
|
|
1
|
-
#
|
|
1
|
+
# cms-renderer
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`cms-renderer` is a library for rendering CMS-powered routes inside a Next.js app.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
CMS (authors content) --> tRPC API --> Website (renders blocks)
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
The website app fetches structured page data via tRPC and renders blocks using the ComponentMap pattern. Article content uses WASM-powered markdown rendering for performance.
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## Quick Start
|
|
5
|
+
It provides:
|
|
14
6
|
|
|
15
|
-
|
|
7
|
+
- A catch-all route component that fetches CMS routes and blocks from the CMS API and renders them with your React component registry
|
|
8
|
+
- A proxy helper for mounting the CMS admin and API behind your app
|
|
9
|
+
- Schema-fetching utilities for headless document access
|
|
10
|
+
- Docs-oriented markdown rendering and styles
|
|
11
|
+
- Type exports for block registries and route params
|
|
16
12
|
|
|
17
|
-
|
|
18
|
-
|------|---------|---------------|
|
|
19
|
-
| Bun | 1.2.8+ | `bun --version` |
|
|
20
|
-
| Node.js | 18+ | `node --version` |
|
|
13
|
+
It is meant to be embedded in an existing Next.js app. It is not a standalone website.
|
|
21
14
|
|
|
22
|
-
|
|
15
|
+
## Install
|
|
23
16
|
|
|
24
17
|
```bash
|
|
25
|
-
|
|
26
|
-
bun install
|
|
27
|
-
|
|
28
|
-
# Start the website dev server
|
|
29
|
-
cd apps/renderer
|
|
30
|
-
bun run dev
|
|
18
|
+
npm install cms-renderer
|
|
31
19
|
```
|
|
32
20
|
|
|
33
|
-
|
|
21
|
+
This package is intended for React + Next.js applications that can reach a running CMS deployment.
|
|
34
22
|
|
|
35
|
-
|
|
23
|
+
## Exported Entry Points
|
|
36
24
|
|
|
37
|
-
|
|
38
|
-
Website
|
|
39
|
-
CMS-powered website built with Next.js 15 and App Router.
|
|
40
|
-
```
|
|
25
|
+
Main entry points:
|
|
41
26
|
|
|
42
|
-
|
|
27
|
+
- `cms-renderer/lib/renderer` for catch-all route rendering
|
|
28
|
+
- `cms-renderer/lib/types` for block and route types
|
|
29
|
+
- `cms-renderer/lib/proxy` for proxying CMS admin and API traffic
|
|
30
|
+
- `cms-renderer/lib/schema` for headless schema-based reads
|
|
31
|
+
- `cms-renderer/lib/custom-schemas` for fetching custom schema metadata and generating Zod code
|
|
32
|
+
- `cms-renderer/lib/docs-markdown` and `cms-renderer/styles/docs-markdown.css` for docs markdown rendering
|
|
33
|
+
- `cms-renderer/lib/refresher` for client-side refresh on CMS updates
|
|
43
34
|
|
|
44
|
-
|
|
35
|
+
Additional utility exports:
|
|
45
36
|
|
|
46
|
-
|
|
37
|
+
- `cms-renderer/lib/block-renderer`
|
|
38
|
+
- `cms-renderer/lib/block-toolbar`
|
|
39
|
+
- `cms-renderer/lib/client-editable-block`
|
|
40
|
+
- `cms-renderer/lib/cms-api`
|
|
41
|
+
- `cms-renderer/lib/markdown-utils`
|
|
42
|
+
- `cms-renderer/lib/result`
|
|
43
|
+
- `cms-renderer/lib/trpc`
|
|
44
|
+
- `cms-renderer/lib/image/lazy-load`
|
|
47
45
|
|
|
48
|
-
|
|
46
|
+
## Core Usage
|
|
49
47
|
|
|
50
|
-
|
|
51
|
-
auteur/
|
|
52
|
-
├── apps/
|
|
53
|
-
│ ├── cms/ # Content authoring (Lexical editor)
|
|
54
|
-
│ └── renderer/ # This app - renders CMS content
|
|
55
|
-
└── packages/
|
|
56
|
-
├── cms-schema/ # Shared Zod schemas
|
|
57
|
-
└── markdown-wasm/ # WASM markdown renderer
|
|
58
|
-
```
|
|
48
|
+
### 1. Render CMS Routes in a Catch-All Next.js Page
|
|
59
49
|
|
|
60
|
-
|
|
50
|
+
Use the default export from `cms-renderer/lib/renderer` inside a catch-all route such as `app/[...slug]/page.tsx`.
|
|
61
51
|
|
|
62
|
-
|
|
52
|
+
```tsx
|
|
53
|
+
import ParametricRoutePage from 'cms-renderer/lib/renderer';
|
|
54
|
+
import type { BlockComponentRegistry } from 'cms-renderer/lib/types';
|
|
55
|
+
import HeaderBlock from '@/components/HeaderBlock';
|
|
63
56
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
│ ├── layout.tsx # Root layout with CSS imports
|
|
73
|
-
│ ├── globals.css # CSS reset
|
|
74
|
-
│ └── page.tsx # Homepage
|
|
75
|
-
├── components/
|
|
76
|
-
│ ├── blocks/ # Block components + registry
|
|
77
|
-
│ │ ├── navigation-block.tsx
|
|
78
|
-
│ │ ├── header-block.tsx
|
|
79
|
-
│ │ ├── article-block.tsx
|
|
80
|
-
│ │ ├── block-renderer.tsx # Dispatcher component
|
|
81
|
-
│ │ ├── types.ts # BlockData discriminated union
|
|
82
|
-
│ │ └── index.ts # Exports + blockComponents registry
|
|
83
|
-
│ ├── page-renderer.tsx # Renders array of blocks
|
|
84
|
-
│ └── trpc-provider.tsx # Client-side tRPC/React Query
|
|
85
|
-
├── server/
|
|
86
|
-
│ ├── trpc.ts # tRPC initialization
|
|
87
|
-
│ └── routers/
|
|
88
|
-
│ ├── blocks/ # Block data procedures
|
|
89
|
-
│ │ ├── get-navigation.ts
|
|
90
|
-
│ │ ├── get-header.ts
|
|
91
|
-
│ │ └── get-article.ts
|
|
92
|
-
│ └── pages/ # Page composition procedures
|
|
93
|
-
│ └── get-page.ts
|
|
94
|
-
├── seed/ # Mock data for development
|
|
95
|
-
│ └── index.ts
|
|
96
|
-
├── styles/
|
|
97
|
-
│ └── blocks.css # Vanilla CSS for blocks
|
|
98
|
-
└── lib/
|
|
99
|
-
└── trpc.ts # Client-side tRPC hooks
|
|
100
|
-
```
|
|
57
|
+
const registry: Partial<BlockComponentRegistry> = {
|
|
58
|
+
header: HeaderBlock,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
interface PageProps {
|
|
62
|
+
params: Promise<{ slug: string[] }>;
|
|
63
|
+
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
|
|
64
|
+
}
|
|
101
65
|
|
|
102
|
-
|
|
66
|
+
export default async function Page({ params, searchParams }: PageProps) {
|
|
67
|
+
const { slug } = await params;
|
|
103
68
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
│
|
|
116
|
-
┌──────────────────────┼──────────────────────┐
|
|
117
|
-
v v v
|
|
118
|
-
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
|
|
119
|
-
│ NavigationBlock│ │ HeaderBlock │ │ ArticleBlock │
|
|
120
|
-
│ │ │ │ │ (WASM markdown)│
|
|
121
|
-
└───────────────┘ └───────────────┘ └───────────────┘
|
|
69
|
+
return (
|
|
70
|
+
<ParametricRoutePage
|
|
71
|
+
registry={registry}
|
|
72
|
+
cmsUrl={process.env.NEXT_PUBLIC_CMS_URL!}
|
|
73
|
+
apiKey={process.env.CMS_API_KEY}
|
|
74
|
+
websiteId={process.env.NEXT_PUBLIC_WEBSITE_ID}
|
|
75
|
+
params={Promise.resolve({ slug })}
|
|
76
|
+
searchParams={searchParams}
|
|
77
|
+
/>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
122
80
|
```
|
|
123
81
|
|
|
124
|
-
|
|
82
|
+
What the route component does:
|
|
125
83
|
|
|
126
|
-
|
|
84
|
+
- Resolves the incoming path from the catch-all slug
|
|
85
|
+
- Fetches the route definition from the CMS API
|
|
86
|
+
- Fetches each referenced block
|
|
87
|
+
- Maps each block to your registry by `schema_name`
|
|
88
|
+
- Renders built-in and custom block types
|
|
89
|
+
- Supports `edit_mode` and `ai_preview` query params
|
|
127
90
|
|
|
128
|
-
###
|
|
91
|
+
### 2. Register Block Components
|
|
129
92
|
|
|
130
|
-
|
|
131
|
-
|--------|---------|-------------|
|
|
132
|
-
| `dev` | `bun run dev` | Start dev server on port 3001 |
|
|
133
|
-
| `build` | `bun run build` | Production build |
|
|
134
|
-
| `start` | `bun run start` | Start production server |
|
|
135
|
-
| `lint` | `bun run lint` | Run Biome linter |
|
|
136
|
-
| `check-types` | `bun run check-types` | TypeScript type checking |
|
|
93
|
+
Block components receive `content` and optional `routeParams`.
|
|
137
94
|
|
|
138
|
-
|
|
95
|
+
```tsx
|
|
96
|
+
import type { BlockComponentProps } from 'cms-renderer/lib/types';
|
|
97
|
+
|
|
98
|
+
type HeroProps = BlockComponentProps<{
|
|
99
|
+
headline: string;
|
|
100
|
+
subheadline?: string;
|
|
101
|
+
}>;
|
|
102
|
+
|
|
103
|
+
export default function HeroBlock({ content }: HeroProps) {
|
|
104
|
+
return (
|
|
105
|
+
<section>
|
|
106
|
+
<h1>{content.headline}</h1>
|
|
107
|
+
{content.subheadline ? <p>{content.subheadline}</p> : null}
|
|
108
|
+
</section>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
```
|
|
139
112
|
|
|
140
|
-
|
|
141
|
-
- **tRPC procedures**: Requires server restart
|
|
142
|
-
- **CSS changes**: Instant hot reload
|
|
143
|
-
- **Block components**: Instant hot reload
|
|
113
|
+
Useful types are exported from `cms-renderer/lib/types`:
|
|
144
114
|
|
|
145
|
-
|
|
115
|
+
- `BlockData`
|
|
116
|
+
- `BlockComponentProps<T>`
|
|
117
|
+
- `BlockComponentRegistry`
|
|
118
|
+
- `ResolvedRouteParams`
|
|
146
119
|
|
|
147
|
-
|
|
120
|
+
### 3. Configure Environment
|
|
148
121
|
|
|
149
|
-
|
|
122
|
+
Required for route rendering:
|
|
150
123
|
|
|
151
124
|
```env
|
|
152
|
-
|
|
153
|
-
|
|
125
|
+
NEXT_PUBLIC_CMS_URL=https://cms.example.com
|
|
126
|
+
NEXT_PUBLIC_WEBSITE_ID=00000000-0000-0000-0000-000000000000
|
|
154
127
|
```
|
|
155
128
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
## Key Concepts
|
|
159
|
-
|
|
160
|
-
### ComponentMap Pattern
|
|
161
|
-
|
|
162
|
-
The website uses a registry-based pattern to map block types to React components:
|
|
129
|
+
Optional:
|
|
163
130
|
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
header: HeaderBlock,
|
|
169
|
-
article: ArticleBlock,
|
|
170
|
-
} as const;
|
|
131
|
+
```env
|
|
132
|
+
CMS_API_KEY=your-api-key
|
|
133
|
+
ADMIN_UPSTREAM_ORIGIN=https://cms.example.com
|
|
134
|
+
NEXT_PUBLIC_CMS_API_URL=https://cms.example.com
|
|
171
135
|
```
|
|
172
136
|
|
|
173
|
-
|
|
137
|
+
`ParametricRoutePage` requires:
|
|
174
138
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
<BlockRenderer block={{ type: 'header', content: {...} }} />
|
|
139
|
+
- `cmsUrl`, passed as a prop
|
|
140
|
+
- a website ID, passed as a prop or available through environment variables
|
|
178
141
|
|
|
179
|
-
|
|
180
|
-
{page.blocks.map((block, i) => (
|
|
181
|
-
<BlockRenderer key={i} block={block} />
|
|
182
|
-
))}
|
|
183
|
-
```
|
|
142
|
+
`websiteId` can be passed explicitly to `ParametricRoutePage`, or it can be read from:
|
|
184
143
|
|
|
185
|
-
|
|
144
|
+
- `NEXT_PUBLIC_WEBSITE_ID`
|
|
145
|
+
- `WEBSITE_ID`
|
|
146
|
+
- `CMS_WEBSITE_ID`
|
|
186
147
|
|
|
187
|
-
|
|
188
|
-
|-------|-----------|----------------|
|
|
189
|
-
| `navigation` | `NavigationBlock` | logo, ariaLabel, links (3-level hierarchy) |
|
|
190
|
-
| `header` | `HeaderBlock` | headline, subheadline, backgroundImage, ctaButton, alignment |
|
|
191
|
-
| `article` | `ArticleBlock` | headline, author, publishedAt, body (markdown), tags |
|
|
148
|
+
Use `CMS_API_KEY` when your CMS requires authenticated access.
|
|
192
149
|
|
|
193
|
-
|
|
150
|
+
`NEXT_PUBLIC_CMS_API_URL` is only used by the default `schema` export from `cms-renderer/lib/schema`. If you call `configureSchema(...)` directly, pass `cmsUrl` explicitly instead.
|
|
194
151
|
|
|
195
|
-
|
|
152
|
+
If no website ID is available, `ParametricRoutePage` throws at runtime.
|
|
196
153
|
|
|
197
|
-
|
|
198
|
-
// Direct procedure call in Server Components
|
|
199
|
-
import { createCaller } from '@/server/routers';
|
|
200
|
-
import { createContext } from '@/server/trpc';
|
|
154
|
+
## CMS Proxy
|
|
201
155
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
const caller = createCaller(createContext());
|
|
205
|
-
const page = await caller.pages.getPage({ slug });
|
|
156
|
+
Use `createCmsProxy` when your app needs to proxy `/admin`, `/api`, `/auth`, and related CMS assets to an upstream CMS deployment.
|
|
157
|
+
Use it from `proxy.ts` or `middleware.ts`, depending on how your Next.js app is structured.
|
|
206
158
|
|
|
207
|
-
|
|
208
|
-
}
|
|
209
|
-
|
|
159
|
+
```ts
|
|
160
|
+
import { createCmsProxy, cmsProxyMatcher } from 'cms-renderer/lib/proxy';
|
|
161
|
+
import type { NextRequest } from 'next/server';
|
|
210
162
|
|
|
211
|
-
|
|
163
|
+
const cmsProxy = createCmsProxy({
|
|
164
|
+
upstream: process.env.ADMIN_UPSTREAM_ORIGIN,
|
|
165
|
+
});
|
|
212
166
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
import { trpc } from '@/lib/trpc';
|
|
216
|
-
|
|
217
|
-
function PageClient({ slug }: { slug: string }) {
|
|
218
|
-
const { data, isLoading } = trpc.pages.getPage.useQuery({ slug });
|
|
219
|
-
|
|
220
|
-
if (isLoading) return <div>Loading...</div>;
|
|
221
|
-
return <PageRenderer title={data.title} blocks={data.blocks} />;
|
|
167
|
+
export async function middleware(request: NextRequest) {
|
|
168
|
+
return cmsProxy(request);
|
|
222
169
|
}
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
### Edit-Mode Block Toolbar
|
|
226
170
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
On `mouseenter`, the block's `DOMRect` is captured and passed to `BlockToolbar`. After the toolbar renders, a `useLayoutEffect` measures the toolbar's actual dimensions and tries up to five candidate placements in order, stopping at the first one that fits fully inside the viewport:
|
|
232
|
-
|
|
233
|
-
| # | Placement |
|
|
234
|
-
|---|-----------|
|
|
235
|
-
| 1 | Below block, centered |
|
|
236
|
-
| 2 | Above block, centered |
|
|
237
|
-
| 3 | Below block, right-aligned |
|
|
238
|
-
| 4 | Below block, left-aligned |
|
|
239
|
-
| 5 | Above block, right-aligned |
|
|
171
|
+
export const config = {
|
|
172
|
+
matcher: cmsProxyMatcher,
|
|
173
|
+
};
|
|
174
|
+
```
|
|
240
175
|
|
|
241
|
-
|
|
176
|
+
The proxy helper:
|
|
242
177
|
|
|
243
|
-
|
|
178
|
+
- Forwards `/admin`, `/api`, and `/auth`
|
|
179
|
+
- Can proxy additional custom path prefixes
|
|
180
|
+
- Rewrites redirects back to the current host when appropriate
|
|
181
|
+
- Rewrites cookies for the current deployment host
|
|
182
|
+
- Proxies admin-originated static asset requests
|
|
244
183
|
|
|
245
|
-
|
|
246
|
-
- `lib/block-toolbar.tsx` — runs the candidate loop in `useLayoutEffect` and applies `top`/`left` directly on the element
|
|
184
|
+
## Headless Schema Access
|
|
247
185
|
|
|
248
|
-
|
|
186
|
+
Use `configureSchema` or the default `schema` export when you want published documents for a custom schema outside the block renderer.
|
|
249
187
|
|
|
250
|
-
|
|
188
|
+
```ts
|
|
189
|
+
import { configureSchema } from 'cms-renderer/lib/schema';
|
|
251
190
|
|
|
252
|
-
|
|
253
|
-
|
|
191
|
+
const cms = configureSchema({
|
|
192
|
+
cmsUrl: process.env.NEXT_PUBLIC_CMS_URL!,
|
|
193
|
+
apiKey: process.env.CMS_API_KEY,
|
|
194
|
+
websiteId: process.env.NEXT_PUBLIC_WEBSITE_ID,
|
|
195
|
+
});
|
|
254
196
|
|
|
255
|
-
|
|
256
|
-
|
|
197
|
+
const posts = await cms.name('post').fetchAll();
|
|
198
|
+
const post = await cms.name('post').fetchSingleById('document-id');
|
|
199
|
+
const frenchPosts = await cms.name('post').translation('fr').fetchAll();
|
|
257
200
|
```
|
|
258
201
|
|
|
259
|
-
|
|
202
|
+
Available methods:
|
|
260
203
|
|
|
261
|
-
|
|
204
|
+
- `fetchAll()`
|
|
205
|
+
- `fetchSingle()`
|
|
206
|
+
- `fetchSingleById(id)`
|
|
207
|
+
- `translation(language)`
|
|
262
208
|
|
|
263
|
-
|
|
209
|
+
If you use the default `schema` export, it reads `NEXT_PUBLIC_CMS_API_URL` from the environment. If your app already has a CMS base URL under a different variable name, prefer `configureSchema(...)` and pass `cmsUrl` explicitly.
|
|
264
210
|
|
|
265
|
-
|
|
211
|
+
## Custom Schema Code Generation
|
|
266
212
|
|
|
267
|
-
|
|
268
|
-
|----------|--------|-------|---------|
|
|
269
|
-
| `blocks.getNavigation` | Query | None | Navigation data |
|
|
270
|
-
| `blocks.getHeader` | Query | None | Header data |
|
|
271
|
-
| `blocks.getArticle` | Query | None | Article data |
|
|
272
|
-
| `pages.getPage` | Query | `{ slug: string }` | Page with blocks array |
|
|
213
|
+
Use `cms-renderer/lib/custom-schemas` to fetch custom schema metadata and generate typed Zod schema code for your app.
|
|
273
214
|
|
|
274
|
-
|
|
215
|
+
```ts
|
|
216
|
+
import {
|
|
217
|
+
fetchAllCustomSchemaFields,
|
|
218
|
+
saveZodSchemaCode,
|
|
219
|
+
} from 'cms-renderer/lib/custom-schemas';
|
|
275
220
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
221
|
+
const schemas = await fetchAllCustomSchemaFields({
|
|
222
|
+
cmsUrl: process.env.NEXT_PUBLIC_CMS_URL!,
|
|
223
|
+
apiKey: process.env.CMS_API_KEY,
|
|
224
|
+
websiteId: process.env.NEXT_PUBLIC_WEBSITE_ID!,
|
|
225
|
+
});
|
|
279
226
|
|
|
280
|
-
|
|
281
|
-
curl "http://localhost:3001/api/trpc/blocks.getNavigation"
|
|
282
|
-
curl "http://localhost:3001/api/trpc/blocks.getHeader"
|
|
283
|
-
curl "http://localhost:3001/api/trpc/pages.getPage?input=%7B%22json%22%3A%7B%22slug%22%3A%22demo%22%7D%7D"
|
|
227
|
+
await saveZodSchemaCode(schemas, 'generated/cms-schemas.ts');
|
|
284
228
|
```
|
|
285
229
|
|
|
286
|
-
|
|
230
|
+
Also exported:
|
|
287
231
|
|
|
288
|
-
|
|
232
|
+
- `fetchCustomSchemaFields(options, schemaName)`
|
|
233
|
+
- `buildZodSchemas(schemas)`
|
|
289
234
|
|
|
290
|
-
|
|
235
|
+
## Docs Markdown
|
|
291
236
|
|
|
292
|
-
|
|
293
|
-
# Type check
|
|
294
|
-
bun run check-types
|
|
237
|
+
`cms-renderer` includes a docs-oriented markdown boundary for starter apps and docs sites.
|
|
295
238
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
Test files would live at:
|
|
301
|
-
- `components/blocks/*.test.tsx` for block components
|
|
302
|
-
- `server/routers/**/*.test.ts` for tRPC procedures
|
|
303
|
-
|
|
304
|
-
---
|
|
305
|
-
|
|
306
|
-
## Troubleshooting
|
|
307
|
-
|
|
308
|
-
### Port 3001 Already in Use
|
|
309
|
-
|
|
310
|
-
```bash
|
|
311
|
-
# Find and kill the process
|
|
312
|
-
lsof -i :3001
|
|
313
|
-
kill -9 <PID>
|
|
239
|
+
```tsx
|
|
240
|
+
import { DocsMarkdown } from 'cms-renderer/lib/docs-markdown';
|
|
241
|
+
import 'cms-renderer/styles/docs-markdown.css';
|
|
314
242
|
|
|
315
|
-
|
|
316
|
-
|
|
243
|
+
export default async function Page() {
|
|
244
|
+
return <DocsMarkdown content={'# Hello\n\nThis came from the CMS.'} />;
|
|
245
|
+
}
|
|
317
246
|
```
|
|
318
247
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
```bash
|
|
322
|
-
# Run bun install from monorepo root
|
|
323
|
-
cd ../..
|
|
324
|
-
bun install
|
|
325
|
-
```
|
|
248
|
+
`DocsMarkdown` supports:
|
|
326
249
|
|
|
327
|
-
|
|
250
|
+
- WASM-backed markdown parsing
|
|
251
|
+
- Syntax-highlighted code blocks
|
|
252
|
+
- Optional custom image rendering via `renderImage`
|
|
253
|
+
- Default styling through `styles/docs-markdown.css`
|
|
328
254
|
|
|
329
|
-
|
|
255
|
+
## Live Refresh
|
|
330
256
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
bun run check-types # Now type checking works
|
|
334
|
-
```
|
|
257
|
+
Use `Refresher` in client components to refresh the current route when CMS content changes.
|
|
258
|
+
You can also provide `onInvalidate` to run app-specific invalidation before `router.refresh()`.
|
|
335
259
|
|
|
336
|
-
|
|
260
|
+
```tsx
|
|
261
|
+
'use client';
|
|
337
262
|
|
|
338
|
-
|
|
263
|
+
import { Refresher } from 'cms-renderer/lib/refresher';
|
|
339
264
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
265
|
+
export function CmsLiveRefresh() {
|
|
266
|
+
return (
|
|
267
|
+
<Refresher
|
|
268
|
+
cmsUrl={process.env.NEXT_PUBLIC_CMS_URL!}
|
|
269
|
+
websiteId={process.env.NEXT_PUBLIC_WEBSITE_ID!}
|
|
270
|
+
apiKey={process.env.NEXT_PUBLIC_CMS_API_KEY}
|
|
271
|
+
/>
|
|
272
|
+
);
|
|
273
|
+
}
|
|
345
274
|
```
|
|
346
275
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
The mock data only includes pages with slugs: `demo`, `about`, `blog`. Requesting other slugs returns a 404.
|
|
276
|
+
## Edit Mode
|
|
350
277
|
|
|
351
|
-
|
|
278
|
+
When the page is rendered with `edit_mode=true` or `edit_mode=1`, the renderer enables editable wrappers and block toolbar behavior for CMS iframe editing.
|
|
352
279
|
|
|
353
|
-
|
|
280
|
+
In normal public traffic, you do not need to do anything special for edit mode.
|
|
281
|
+
This mode is intended for CMS-integrated editing flows, not for public pages.
|
|
354
282
|
|
|
355
|
-
|
|
356
|
-
- **CMS App**: [`apps/cms/README.md`](../cms/README.md) - Content authoring application
|
|
357
|
-
- **Schema Package**: [`packages/cms-schema/`](../../packages/cms-schema/) - Shared Zod schemas
|
|
358
|
-
- **Markdown Package**: [`packages/markdown-wasm/`](../../packages/markdown-wasm/) - WASM renderer
|
|
283
|
+
## Contributor Notes
|
|
359
284
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
## Contributing
|
|
363
|
-
|
|
364
|
-
### Code Style
|
|
365
|
-
|
|
366
|
-
This project uses [Biome](https://biomejs.dev/) for linting and formatting:
|
|
285
|
+
Useful package scripts:
|
|
367
286
|
|
|
368
287
|
```bash
|
|
369
|
-
bun run
|
|
370
|
-
bun run
|
|
288
|
+
bun run build
|
|
289
|
+
bun run check-types
|
|
290
|
+
bun run lint
|
|
291
|
+
bun run test
|
|
292
|
+
bun run test:coverage
|
|
371
293
|
```
|
|
372
294
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
1. Define the schema in `apps/cms/app/schemas/`
|
|
376
|
-
2. Add mock data to `seed/index.ts`
|
|
377
|
-
3. Create the component in `components/blocks/`
|
|
378
|
-
4. Add to `blockComponents` registry in `components/blocks/index.ts`
|
|
379
|
-
5. Add the type to `BlockData` union in `components/blocks/types.ts`
|
|
380
|
-
|
|
381
|
-
### Pull Requests
|
|
382
|
-
|
|
383
|
-
- Run `bun run check-types` before submitting
|
|
384
|
-
- Run `bun run lint` to ensure code style compliance
|
|
385
|
-
- Test the demo page at `/demo` renders correctly
|
|
295
|
+
`dist/` is generated by `tsup` during build and publish.
|
|
@@ -216,7 +216,8 @@ function BlockRenderer({
|
|
|
216
216
|
}
|
|
217
217
|
return null;
|
|
218
218
|
}
|
|
219
|
-
const
|
|
219
|
+
const language = routeParams ? Object.values(routeParams).find((p) => p.schemaName === "language")?.value : void 0;
|
|
220
|
+
const component = /* @__PURE__ */ jsx(Component, { content: block.content, routeParams, language });
|
|
220
221
|
if (disableEditable) {
|
|
221
222
|
return component;
|
|
222
223
|
}
|