@coherent.js/nextjs 1.0.0-beta.2 → 1.0.0-beta.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 +521 -0
- package/dist/index.cjs +75 -2038
- package/dist/index.cjs.map +4 -4
- package/dist/index.js +75 -2028
- package/dist/index.js.map +4 -4
- package/dist/nextjs/coherent-nextjs.js +24 -24
- package/dist/nextjs/index.js +1 -1
- package/package.json +3 -2
package/README.md
ADDED
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
# @coherent.js/nextjs
|
|
2
|
+
|
|
3
|
+
Next.js integration for Coherent.js - Bring Coherent.js components to your Next.js applications.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @coherent.js/nextjs
|
|
9
|
+
# or
|
|
10
|
+
pnpm add @coherent.js/nextjs
|
|
11
|
+
# or
|
|
12
|
+
yarn add @coherent.js/nextjs
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**Note:** You also need to have Next.js and Coherent.js core installed:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install next @coherent.js/core
|
|
19
|
+
# or
|
|
20
|
+
pnpm add next @coherent.js/core
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Overview
|
|
24
|
+
|
|
25
|
+
The `@coherent.js/nextjs` package provides seamless integration between Coherent.js and Next.js, allowing you to use Coherent.js components alongside React components in your Next.js applications. This integration enables:
|
|
26
|
+
|
|
27
|
+
- Server-side rendering with Coherent.js
|
|
28
|
+
- Client-side hydration
|
|
29
|
+
- Seamless integration with Next.js routing
|
|
30
|
+
- Performance benefits of both frameworks
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
### 1. Create a Coherent.js Component
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
// components/HelloWorld.js
|
|
38
|
+
export function HelloWorld({ name = 'World' }) {
|
|
39
|
+
return {
|
|
40
|
+
div: {
|
|
41
|
+
className: 'hello-world',
|
|
42
|
+
children: [
|
|
43
|
+
{ h1: { text: `Hello, ${name}!` } },
|
|
44
|
+
{ p: { text: 'This component is rendered with Coherent.js' } }
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 2. Use in a Next.js Page
|
|
52
|
+
|
|
53
|
+
```javascript
|
|
54
|
+
// pages/index.js
|
|
55
|
+
import { renderCoherentComponent } from '@coherent.js/nextjs';
|
|
56
|
+
import { HelloWorld } from '../components/HelloWorld';
|
|
57
|
+
|
|
58
|
+
export default function Home() {
|
|
59
|
+
// Render Coherent.js component to React element
|
|
60
|
+
const coherentElement = renderCoherentComponent(HelloWorld, {
|
|
61
|
+
name: 'Next.js User'
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div>
|
|
66
|
+
<h1>Welcome to Next.js</h1>
|
|
67
|
+
{coherentElement}
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Features
|
|
74
|
+
|
|
75
|
+
### Server-Side Rendering
|
|
76
|
+
|
|
77
|
+
Automatic server-side rendering of Coherent.js components:
|
|
78
|
+
|
|
79
|
+
```javascript
|
|
80
|
+
// pages/profile.js
|
|
81
|
+
import { renderCoherentComponent } from '@coherent.js/nextjs';
|
|
82
|
+
import { UserProfile } from '../components/UserProfile';
|
|
83
|
+
|
|
84
|
+
export async function getServerSideProps(context) {
|
|
85
|
+
// Fetch user data on server
|
|
86
|
+
const user = await fetchUser(context.params.id);
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
props: {
|
|
90
|
+
user
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export default function Profile({ user }) {
|
|
96
|
+
const userProfile = renderCoherentComponent(UserProfile, {
|
|
97
|
+
user,
|
|
98
|
+
editable: false // No editing on server-rendered page
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<div>
|
|
103
|
+
<h1>User Profile</h1>
|
|
104
|
+
{userProfile}
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Client-Side Hydration
|
|
111
|
+
|
|
112
|
+
Enable full interactivity with client-side hydration:
|
|
113
|
+
|
|
114
|
+
```javascript
|
|
115
|
+
// components/Counter.js
|
|
116
|
+
import { withState } from '@coherent.js/core';
|
|
117
|
+
|
|
118
|
+
export const Counter = withState({ count: 0 })(({ state, setState }) => {
|
|
119
|
+
const increment = () => setState({ count: state.count + 1 });
|
|
120
|
+
const decrement = () => setState({ count: state.count - 1 });
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
div: {
|
|
124
|
+
'data-coherent-component': 'counter', // Required for hydration
|
|
125
|
+
children: [
|
|
126
|
+
{ p: { text: `Count: ${state.count}` } },
|
|
127
|
+
{ button: { text: '+', onclick: increment } },
|
|
128
|
+
{ button: { text: '-', onclick: decrement } }
|
|
129
|
+
]
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
```javascript
|
|
136
|
+
// pages/counter.js
|
|
137
|
+
import { renderCoherentComponent } from '@coherent.js/nextjs';
|
|
138
|
+
import { Counter } from '../components/Counter';
|
|
139
|
+
|
|
140
|
+
export default function CounterPage() {
|
|
141
|
+
const counter = renderCoherentComponent(Counter, {}, {
|
|
142
|
+
hydrate: true // Enable client-side hydration
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<div>
|
|
147
|
+
<h1>Interactive Counter</h1>
|
|
148
|
+
{counter}
|
|
149
|
+
</div>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Integration with Next.js Data Fetching
|
|
155
|
+
|
|
156
|
+
Seamlessly integrate with Next.js data fetching methods:
|
|
157
|
+
|
|
158
|
+
```javascript
|
|
159
|
+
// pages/products.js
|
|
160
|
+
import { renderCoherentComponent } from '@coherent.js/nextjs';
|
|
161
|
+
import { ProductList } from '../components/ProductList';
|
|
162
|
+
|
|
163
|
+
export async function getStaticProps() {
|
|
164
|
+
// Fetch products at build time
|
|
165
|
+
const products = await fetchProducts();
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
props: {
|
|
169
|
+
products
|
|
170
|
+
},
|
|
171
|
+
revalidate: 60 // Revalidate every 60 seconds
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export default function Products({ products }) {
|
|
176
|
+
const productList = renderCoherentComponent(ProductList, {
|
|
177
|
+
products
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
<div>
|
|
182
|
+
<h1>Products</h1>
|
|
183
|
+
{productList}
|
|
184
|
+
</div>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Configuration
|
|
190
|
+
|
|
191
|
+
### Custom Hydration Setup
|
|
192
|
+
|
|
193
|
+
Configure hydration behavior:
|
|
194
|
+
|
|
195
|
+
```javascript
|
|
196
|
+
// pages/_app.js
|
|
197
|
+
import { setupHydration } from '@coherent.js/nextjs';
|
|
198
|
+
import { Counter } from '../components/Counter';
|
|
199
|
+
import { UserProfile } from '../components/UserProfile';
|
|
200
|
+
|
|
201
|
+
// Register components for hydration
|
|
202
|
+
setupHydration({
|
|
203
|
+
counter: Counter,
|
|
204
|
+
userProfile: UserProfile
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
export default function MyApp({ Component, pageProps }) {
|
|
208
|
+
return <Component {...pageProps} />;
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### CSS Integration
|
|
213
|
+
|
|
214
|
+
Handle CSS with Coherent.js components:
|
|
215
|
+
|
|
216
|
+
```javascript
|
|
217
|
+
// components/StyledComponent.js
|
|
218
|
+
export function StyledComponent() {
|
|
219
|
+
return {
|
|
220
|
+
div: {
|
|
221
|
+
className: 'styled-component',
|
|
222
|
+
style: {
|
|
223
|
+
backgroundColor: '#f0f0f0',
|
|
224
|
+
padding: '20px',
|
|
225
|
+
borderRadius: '8px'
|
|
226
|
+
},
|
|
227
|
+
children: [
|
|
228
|
+
{ h2: { text: 'Styled with Coherent.js' } },
|
|
229
|
+
{ p: { text: 'This component has inline styles' } }
|
|
230
|
+
]
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Template Customization
|
|
237
|
+
|
|
238
|
+
Customize the HTML template for Coherent.js components:
|
|
239
|
+
|
|
240
|
+
```javascript
|
|
241
|
+
// pages/custom.js
|
|
242
|
+
import { renderCoherentComponent } from '@coherent.js/nextjs';
|
|
243
|
+
import { CustomComponent } from '../components/CustomComponent';
|
|
244
|
+
|
|
245
|
+
export default function CustomPage() {
|
|
246
|
+
const component = renderCoherentComponent(CustomComponent, {
|
|
247
|
+
data: 'example'
|
|
248
|
+
}, {
|
|
249
|
+
template: ({ html, head, body }) => `
|
|
250
|
+
<div class="custom-wrapper">
|
|
251
|
+
<header>Custom Header</header>
|
|
252
|
+
<main>${body}</main>
|
|
253
|
+
<footer>Custom Footer</footer>
|
|
254
|
+
</div>
|
|
255
|
+
`
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
return <div>{component}</div>;
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Performance Optimizations
|
|
263
|
+
|
|
264
|
+
### Code Splitting
|
|
265
|
+
|
|
266
|
+
Leverage Next.js code splitting with Coherent.js:
|
|
267
|
+
|
|
268
|
+
```javascript
|
|
269
|
+
// components/LazyComponent.js
|
|
270
|
+
export function LazyComponent({ message }) {
|
|
271
|
+
return {
|
|
272
|
+
div: {
|
|
273
|
+
className: 'lazy-component',
|
|
274
|
+
children: [
|
|
275
|
+
{ h3: { text: 'Lazy Loaded Component' } },
|
|
276
|
+
{ p: { text: message } }
|
|
277
|
+
]
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
```javascript
|
|
284
|
+
// pages/lazy.js
|
|
285
|
+
import { renderCoherentComponent } from '@coherent.js/nextjs';
|
|
286
|
+
import dynamic from 'next/dynamic';
|
|
287
|
+
|
|
288
|
+
// Dynamically import Coherent.js component
|
|
289
|
+
const LazyComponent = dynamic(() =>
|
|
290
|
+
import('../components/LazyComponent').then(mod => mod.LazyComponent)
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
export default function LazyPage() {
|
|
294
|
+
return (
|
|
295
|
+
<div>
|
|
296
|
+
<h1>Lazy Loading Example</h1>
|
|
297
|
+
<LazyComponent message="This component was loaded dynamically" />
|
|
298
|
+
</div>
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Caching Strategies
|
|
304
|
+
|
|
305
|
+
Implement caching for better performance:
|
|
306
|
+
|
|
307
|
+
```javascript
|
|
308
|
+
// pages/cached.js
|
|
309
|
+
import { renderCoherentComponent } from '@coherent.js/nextjs';
|
|
310
|
+
|
|
311
|
+
export async function getStaticProps() {
|
|
312
|
+
// Cache expensive operations
|
|
313
|
+
const data = await fetchWithCache('expensive-api-call');
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
props: { data },
|
|
317
|
+
revalidate: 3600 // Cache for 1 hour
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export default function CachedPage({ data }) {
|
|
322
|
+
const component = renderCoherentComponent(DataDisplay, { data });
|
|
323
|
+
return <div>{component}</div>;
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## Advanced Usage
|
|
328
|
+
|
|
329
|
+
### State Management Integration
|
|
330
|
+
|
|
331
|
+
Integrate with global state management:
|
|
332
|
+
|
|
333
|
+
```javascript
|
|
334
|
+
// components/ShoppingCart.js
|
|
335
|
+
import { withState } from '@coherent.js/core';
|
|
336
|
+
|
|
337
|
+
export const ShoppingCart = withState({
|
|
338
|
+
items: [],
|
|
339
|
+
total: 0
|
|
340
|
+
})(({ state, setState }) => {
|
|
341
|
+
const addItem = (item) => {
|
|
342
|
+
const newItems = [...state.items, item];
|
|
343
|
+
const newTotal = newItems.reduce((sum, item) => sum + item.price, 0);
|
|
344
|
+
setState({ items: newItems, total: newTotal });
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
return {
|
|
348
|
+
div: {
|
|
349
|
+
'data-coherent-component': 'shopping-cart',
|
|
350
|
+
children: [
|
|
351
|
+
{ h3: { text: 'Shopping Cart' } },
|
|
352
|
+
{ p: { text: `Items: ${state.items.length}` } },
|
|
353
|
+
{ p: { text: `Total: $${state.total.toFixed(2)}` } },
|
|
354
|
+
{ button: {
|
|
355
|
+
text: 'Add Item',
|
|
356
|
+
onclick: () => addItem({ id: Date.now(), name: 'Product', price: 29.99 })
|
|
357
|
+
}}
|
|
358
|
+
]
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
});
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### API Route Integration
|
|
365
|
+
|
|
366
|
+
Use Coherent.js with Next.js API routes:
|
|
367
|
+
|
|
368
|
+
```javascript
|
|
369
|
+
// pages/api/render-component.js
|
|
370
|
+
import { renderToString } from '@coherent.js/core';
|
|
371
|
+
|
|
372
|
+
export default async function handler(req, res) {
|
|
373
|
+
const { componentName, props } = req.body;
|
|
374
|
+
|
|
375
|
+
// Dynamically render component
|
|
376
|
+
let html;
|
|
377
|
+
switch (componentName) {
|
|
378
|
+
case 'HelloWorld':
|
|
379
|
+
const { HelloWorld } = await import('../../components/HelloWorld');
|
|
380
|
+
html = await renderToString(HelloWorld, props);
|
|
381
|
+
break;
|
|
382
|
+
default:
|
|
383
|
+
return res.status(400).json({ error: 'Unknown component' });
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
res.status(200).json({ html });
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
## API Reference
|
|
391
|
+
|
|
392
|
+
### renderCoherentComponent(component, props, options)
|
|
393
|
+
|
|
394
|
+
Render a Coherent.js component as a React element.
|
|
395
|
+
|
|
396
|
+
**Parameters:**
|
|
397
|
+
- `component` - Coherent.js component function
|
|
398
|
+
- `props` - Component props object
|
|
399
|
+
- `options.hydrate` - Enable client-side hydration (default: false)
|
|
400
|
+
- `options.template` - Custom HTML template function
|
|
401
|
+
- `options.context` - Additional context data
|
|
402
|
+
|
|
403
|
+
**Returns:** React element
|
|
404
|
+
|
|
405
|
+
### setupHydration(componentMap)
|
|
406
|
+
|
|
407
|
+
Configure client-side hydration for components.
|
|
408
|
+
|
|
409
|
+
**Parameters:**
|
|
410
|
+
- `componentMap` - Object mapping component names to component functions
|
|
411
|
+
|
|
412
|
+
### Options
|
|
413
|
+
|
|
414
|
+
- `hydrate` - Boolean to enable hydration
|
|
415
|
+
- `template` - Function to customize HTML template
|
|
416
|
+
- `context` - Additional context data for rendering
|
|
417
|
+
|
|
418
|
+
## Examples
|
|
419
|
+
|
|
420
|
+
### E-commerce Product Page
|
|
421
|
+
|
|
422
|
+
```javascript
|
|
423
|
+
// components/ProductPage.js
|
|
424
|
+
import { withState } from '@coherent.js/core';
|
|
425
|
+
|
|
426
|
+
export const ProductPage = withState({
|
|
427
|
+
quantity: 1,
|
|
428
|
+
selectedVariant: null
|
|
429
|
+
})(({ state, setState, product }) => {
|
|
430
|
+
const updateQuantity = (qty) => setState({ quantity: Math.max(1, qty) });
|
|
431
|
+
const selectVariant = (variant) => setState({ selectedVariant: variant });
|
|
432
|
+
|
|
433
|
+
const selectedVariant = state.selectedVariant || product.variants[0];
|
|
434
|
+
const totalPrice = selectedVariant.price * state.quantity;
|
|
435
|
+
|
|
436
|
+
return {
|
|
437
|
+
div: {
|
|
438
|
+
'data-coherent-component': 'product-page',
|
|
439
|
+
className: 'product-page',
|
|
440
|
+
children: [
|
|
441
|
+
{ h1: { text: product.name } },
|
|
442
|
+
{ img: { src: selectedVariant.image, alt: product.name } },
|
|
443
|
+
{
|
|
444
|
+
div: {
|
|
445
|
+
children: product.variants.map(variant => ({
|
|
446
|
+
button: {
|
|
447
|
+
text: variant.name,
|
|
448
|
+
className: variant.id === state.selectedVariant?.id ? 'selected' : '',
|
|
449
|
+
onclick: () => selectVariant(variant)
|
|
450
|
+
}
|
|
451
|
+
}))
|
|
452
|
+
}
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
div: {
|
|
456
|
+
children: [
|
|
457
|
+
{ label: { text: 'Quantity:' } },
|
|
458
|
+
{ input: {
|
|
459
|
+
type: 'number',
|
|
460
|
+
value: state.quantity,
|
|
461
|
+
oninput: (e) => updateQuantity(parseInt(e.target.value))
|
|
462
|
+
}},
|
|
463
|
+
{ p: { text: `Total: $${totalPrice.toFixed(2)}` } },
|
|
464
|
+
{ button: {
|
|
465
|
+
text: 'Add to Cart',
|
|
466
|
+
onclick: () => addToCart({
|
|
467
|
+
productId: product.id,
|
|
468
|
+
variantId: selectedVariant.id,
|
|
469
|
+
quantity: state.quantity
|
|
470
|
+
})
|
|
471
|
+
}}
|
|
472
|
+
]
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
]
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
});
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
```javascript
|
|
482
|
+
// pages/products/[id].js
|
|
483
|
+
import { renderCoherentComponent } from '@coherent.js/nextjs';
|
|
484
|
+
import { ProductPage } from '../../components/ProductPage';
|
|
485
|
+
|
|
486
|
+
export async function getServerSideProps({ params }) {
|
|
487
|
+
const product = await fetchProduct(params.id);
|
|
488
|
+
|
|
489
|
+
return {
|
|
490
|
+
props: {
|
|
491
|
+
product
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
export default function Product({ product }) {
|
|
497
|
+
const productPage = renderCoherentComponent(ProductPage, {
|
|
498
|
+
product
|
|
499
|
+
}, {
|
|
500
|
+
hydrate: true
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
return (
|
|
504
|
+
<div className="container">
|
|
505
|
+
{productPage}
|
|
506
|
+
</div>
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
## Related Packages
|
|
512
|
+
|
|
513
|
+
- [@coherent.js/core](../core/README.md) - Core framework
|
|
514
|
+
- [@coherent.js/client](../client/README.md) - Client-side utilities
|
|
515
|
+
- [@coherent.js/express](../express/README.md) - Express.js adapter
|
|
516
|
+
- [@coherent.js/fastify](../fastify/README.md) - Fastify adapter
|
|
517
|
+
- [@coherent.js/koa](../koa/README.md) - Koa adapter
|
|
518
|
+
|
|
519
|
+
## License
|
|
520
|
+
|
|
521
|
+
MIT
|