cms-renderer 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/.turbo/turbo-check-types.log +2 -0
- package/README.md +362 -0
- package/lib/__tests__/enrich-block-images.test.ts +394 -0
- package/lib/block-renderer.tsx +60 -0
- package/lib/cms-api.ts +86 -0
- package/lib/data-utils.ts +572 -0
- package/lib/image/lazy-load.ts +209 -0
- package/lib/markdown-utils.ts +368 -0
- package/lib/renderer.tsx +189 -0
- package/lib/result.ts +450 -0
- package/lib/schema.ts +74 -0
- package/lib/trpc.ts +28 -0
- package/lib/types.ts +201 -0
- package/next.config.ts +39 -0
- package/package.json +61 -0
- package/postcss.config.mjs +5 -0
- package/tsconfig.json +12 -0
- package/tsconfig.tsbuildinfo +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
# Website
|
|
2
|
+
|
|
3
|
+
**CMS-powered website built with Next.js 15 and App Router**
|
|
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
|
|
14
|
+
|
|
15
|
+
**Prerequisites**
|
|
16
|
+
|
|
17
|
+
| Tool | Version | Check Command |
|
|
18
|
+
|------|---------|---------------|
|
|
19
|
+
| Bun | 1.2.8+ | `bun --version` |
|
|
20
|
+
| Node.js | 18+ | `node --version` |
|
|
21
|
+
|
|
22
|
+
**Installation**
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# From monorepo root
|
|
26
|
+
bun install
|
|
27
|
+
|
|
28
|
+
# Start the website dev server
|
|
29
|
+
cd apps/website
|
|
30
|
+
bun run dev
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Verification**
|
|
34
|
+
|
|
35
|
+
Open [http://localhost:3001](http://localhost:3001). You should see:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
Website
|
|
39
|
+
CMS-powered website built with Next.js 15 and App Router.
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Visit [http://localhost:3001/demo](http://localhost:3001/demo) to see the complete demo page with navigation, header, and article blocks.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Architecture
|
|
47
|
+
|
|
48
|
+
### How This App Fits in the Monorepo
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
auteur/
|
|
52
|
+
├── apps/
|
|
53
|
+
│ ├── cms/ # Content authoring (Lexical editor)
|
|
54
|
+
│ └── website/ # This app - renders CMS content
|
|
55
|
+
└── packages/
|
|
56
|
+
├── cms-schema/ # Shared Zod schemas
|
|
57
|
+
└── markdown-wasm/ # WASM markdown renderer
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The website consumes schemas from `@repo/cms-schema` and uses `@repo/markdown-wasm` for article body rendering. It does not import from `apps/cms/` directly.
|
|
61
|
+
|
|
62
|
+
### Key Directories
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
apps/website/
|
|
66
|
+
├── app/ # Next.js App Router pages
|
|
67
|
+
│ ├── [slug]/ # Dynamic page routes
|
|
68
|
+
│ │ ├── page.tsx # Server Component fetches via tRPC
|
|
69
|
+
│ │ ├── loading.tsx # Skeleton loading state
|
|
70
|
+
│ │ └── error.tsx # Error boundary
|
|
71
|
+
│ ├── api/trpc/[trpc]/ # tRPC API handler
|
|
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
|
+
```
|
|
101
|
+
|
|
102
|
+
### Data Flow
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
106
|
+
│ Dynamic Route │────>│ tRPC Caller │────>│ Mock Data │
|
|
107
|
+
│ [slug]/page │ │ pages.getPage │ │ seed/index.ts │
|
|
108
|
+
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
109
|
+
│
|
|
110
|
+
v
|
|
111
|
+
┌─────────────────┐ ┌─────────────────┐
|
|
112
|
+
│ PageRenderer │────>│ BlockRenderer │
|
|
113
|
+
│ (iterates) │ │ (dispatches) │
|
|
114
|
+
└─────────────────┘ └─────────────────┘
|
|
115
|
+
│
|
|
116
|
+
┌──────────────────────┼──────────────────────┐
|
|
117
|
+
v v v
|
|
118
|
+
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
|
|
119
|
+
│ NavigationBlock│ │ HeaderBlock │ │ ArticleBlock │
|
|
120
|
+
│ │ │ │ │ (WASM markdown)│
|
|
121
|
+
└───────────────┘ └───────────────┘ └───────────────┘
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Development Guide
|
|
127
|
+
|
|
128
|
+
### Available Scripts
|
|
129
|
+
|
|
130
|
+
| Script | Command | Description |
|
|
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 |
|
|
137
|
+
|
|
138
|
+
### Hot Reload Behavior
|
|
139
|
+
|
|
140
|
+
- **Page changes**: Instant hot reload
|
|
141
|
+
- **tRPC procedures**: Requires server restart
|
|
142
|
+
- **CSS changes**: Instant hot reload
|
|
143
|
+
- **Block components**: Instant hot reload
|
|
144
|
+
|
|
145
|
+
### Environment Variables
|
|
146
|
+
|
|
147
|
+
No environment variables are required for development. The app uses mock data from `seed/index.ts`.
|
|
148
|
+
|
|
149
|
+
For production with a real database, you would add:
|
|
150
|
+
|
|
151
|
+
```env
|
|
152
|
+
# .env.local (not required for development)
|
|
153
|
+
DATABASE_URL=your-database-url
|
|
154
|
+
```
|
|
155
|
+
|
|
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:
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
// components/blocks/index.ts
|
|
166
|
+
export const blockComponents: BlockComponentRegistry = {
|
|
167
|
+
navigation: NavigationBlock,
|
|
168
|
+
header: HeaderBlock,
|
|
169
|
+
article: ArticleBlock,
|
|
170
|
+
} as const;
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
The `BlockRenderer` uses this registry to dispatch blocks:
|
|
174
|
+
|
|
175
|
+
```tsx
|
|
176
|
+
// Render any block by type
|
|
177
|
+
<BlockRenderer block={{ type: 'header', content: {...} }} />
|
|
178
|
+
|
|
179
|
+
// Render a page of blocks
|
|
180
|
+
{page.blocks.map((block, i) => (
|
|
181
|
+
<BlockRenderer key={i} block={block} />
|
|
182
|
+
))}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Block Types
|
|
186
|
+
|
|
187
|
+
| Block | Component | Content Fields |
|
|
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 |
|
|
192
|
+
|
|
193
|
+
### tRPC Integration
|
|
194
|
+
|
|
195
|
+
**Server Component** (recommended):
|
|
196
|
+
|
|
197
|
+
```tsx
|
|
198
|
+
// Direct procedure call in Server Components
|
|
199
|
+
import { createCaller } from '@/server/routers';
|
|
200
|
+
import { createContext } from '@/server/trpc';
|
|
201
|
+
|
|
202
|
+
export default async function Page({ params }: PageProps) {
|
|
203
|
+
const { slug } = await params;
|
|
204
|
+
const caller = createCaller(createContext());
|
|
205
|
+
const page = await caller.pages.getPage({ slug });
|
|
206
|
+
|
|
207
|
+
return <PageRenderer title={page.title} blocks={page.blocks} />;
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Client Component** (when needed):
|
|
212
|
+
|
|
213
|
+
```tsx
|
|
214
|
+
'use client';
|
|
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} />;
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### WASM Markdown Rendering
|
|
226
|
+
|
|
227
|
+
The `ArticleBlock` uses `@repo/markdown-wasm` for fast markdown-to-React conversion:
|
|
228
|
+
|
|
229
|
+
```tsx
|
|
230
|
+
import { MarkdownRenderer } from '@repo/markdown-wasm';
|
|
231
|
+
|
|
232
|
+
// Inside ArticleBlock
|
|
233
|
+
<MarkdownRenderer content={body} className="article-block__content" />
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
md4w parses markdown 2.5x faster than JavaScript alternatives and outputs a traversable AST that maps directly to React components.
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## API Routes Reference
|
|
241
|
+
|
|
242
|
+
### tRPC Endpoints
|
|
243
|
+
|
|
244
|
+
| Endpoint | Method | Input | Returns |
|
|
245
|
+
|----------|--------|-------|---------|
|
|
246
|
+
| `blocks.getNavigation` | Query | None | Navigation data |
|
|
247
|
+
| `blocks.getHeader` | Query | None | Header data |
|
|
248
|
+
| `blocks.getArticle` | Query | None | Article data |
|
|
249
|
+
| `pages.getPage` | Query | `{ slug: string }` | Page with blocks array |
|
|
250
|
+
|
|
251
|
+
### Testing Endpoints
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
# Start the dev server
|
|
255
|
+
bun run dev
|
|
256
|
+
|
|
257
|
+
# Test endpoints (in another terminal)
|
|
258
|
+
curl "http://localhost:3001/api/trpc/blocks.getNavigation"
|
|
259
|
+
curl "http://localhost:3001/api/trpc/blocks.getHeader"
|
|
260
|
+
curl "http://localhost:3001/api/trpc/pages.getPage?input=%7B%22json%22%3A%7B%22slug%22%3A%22demo%22%7D%7D"
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Testing
|
|
266
|
+
|
|
267
|
+
The website app does not currently have unit tests. Integration testing is done via the tRPC endpoints.
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
# Type check
|
|
271
|
+
bun run check-types
|
|
272
|
+
|
|
273
|
+
# Lint
|
|
274
|
+
bun run lint
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Test files would live at:
|
|
278
|
+
- `components/blocks/*.test.tsx` for block components
|
|
279
|
+
- `server/routers/**/*.test.ts` for tRPC procedures
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## Troubleshooting
|
|
284
|
+
|
|
285
|
+
### Port 3001 Already in Use
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
# Find and kill the process
|
|
289
|
+
lsof -i :3001
|
|
290
|
+
kill -9 <PID>
|
|
291
|
+
|
|
292
|
+
# Or use a different port
|
|
293
|
+
bun run dev -- --port 3002
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Cannot Find Module '@repo/cms-schema'
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
# Run bun install from monorepo root
|
|
300
|
+
cd ../..
|
|
301
|
+
bun install
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### TypeScript Errors on First Run
|
|
305
|
+
|
|
306
|
+
The `.next/types` directory is generated on first dev server run:
|
|
307
|
+
|
|
308
|
+
```bash
|
|
309
|
+
bun run dev # Run once to generate types
|
|
310
|
+
bun run check-types # Now type checking works
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### WASM Module Not Found
|
|
314
|
+
|
|
315
|
+
If you see errors about md4w:
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
# Rebuild the markdown-wasm package
|
|
319
|
+
cd ../../packages/markdown-wasm
|
|
320
|
+
bun install
|
|
321
|
+
bun run build
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### tRPC 'NOT_FOUND' Error
|
|
325
|
+
|
|
326
|
+
The mock data only includes pages with slugs: `demo`, `about`, `blog`. Requesting other slugs returns a 404.
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## Related Documentation
|
|
331
|
+
|
|
332
|
+
- **Tutorial**: [`_docs/cms-website-integration/`](../../_docs/cms-website-integration/00-overview.md) - Full 11-chapter tutorial
|
|
333
|
+
- **CMS App**: [`apps/cms/README.md`](../cms/README.md) - Content authoring application
|
|
334
|
+
- **Schema Package**: [`packages/cms-schema/`](../../packages/cms-schema/) - Shared Zod schemas
|
|
335
|
+
- **Markdown Package**: [`packages/markdown-wasm/`](../../packages/markdown-wasm/) - WASM renderer
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## Contributing
|
|
340
|
+
|
|
341
|
+
### Code Style
|
|
342
|
+
|
|
343
|
+
This project uses [Biome](https://biomejs.dev/) for linting and formatting:
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
bun run lint # Check for issues
|
|
347
|
+
bun run lint --fix # Auto-fix issues
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### Adding a New Block Type
|
|
351
|
+
|
|
352
|
+
1. Define the schema in `apps/cms/app/schemas/`
|
|
353
|
+
2. Add mock data to `seed/index.ts`
|
|
354
|
+
3. Create the component in `components/blocks/`
|
|
355
|
+
4. Add to `blockComponents` registry in `components/blocks/index.ts`
|
|
356
|
+
5. Add the type to `BlockData` union in `components/blocks/types.ts`
|
|
357
|
+
|
|
358
|
+
### Pull Requests
|
|
359
|
+
|
|
360
|
+
- Run `bun run check-types` before submitting
|
|
361
|
+
- Run `bun run lint` to ensure code style compliance
|
|
362
|
+
- Test the demo page at `/demo` renders correctly
|