@nestjs-ssr/react 0.1.1
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/LICENSE +22 -0
- package/README.md +587 -0
- package/dist/index-Bptct1Q3.d.mts +419 -0
- package/dist/index-Bptct1Q3.d.ts +419 -0
- package/dist/index.d.mts +220 -0
- package/dist/index.d.ts +220 -0
- package/dist/index.js +8148 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +8113 -0
- package/dist/index.mjs.map +1 -0
- package/dist/render/index.d.mts +8 -0
- package/dist/render/index.d.ts +8 -0
- package/dist/render/index.js +1110 -0
- package/dist/render/index.js.map +1 -0
- package/dist/render/index.mjs +1102 -0
- package/dist/render/index.mjs.map +1 -0
- package/dist/templates/entry-client.tsx +20 -0
- package/dist/templates/entry-server.tsx +13 -0
- package/dist/templates/index.html +17 -0
- package/dist/vite/index.d.mts +11 -0
- package/dist/vite/index.d.ts +11 -0
- package/dist/vite/index.js +7022 -0
- package/dist/vite/index.js.map +1 -0
- package/dist/vite/index.mjs +6997 -0
- package/dist/vite/index.mjs.map +1 -0
- package/package.json +135 -0
- package/src/global.d.ts +32 -0
- package/src/templates/entry-client.tsx +20 -0
- package/src/templates/entry-server.tsx +13 -0
- package/src/templates/index.html +17 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
(The MIT License)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Georgi Alexandrov <https://github.com/georgialexandrov>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
'Software'), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
# @nestjs-ssr/react
|
|
2
|
+
|
|
3
|
+
**React SSR for NestJS that respects Clean Architecture.**
|
|
4
|
+
|
|
5
|
+
A lightweight, production-ready library that brings React to NestJS while preserving the architectural principles that make NestJS great: **Dependency Injection**, **SOLID principles**, and **clear separation of concerns**.
|
|
6
|
+
|
|
7
|
+
## Why This Library?
|
|
8
|
+
|
|
9
|
+
### Built for Clean Architecture
|
|
10
|
+
|
|
11
|
+
If you value [Uncle Bob's Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) and software craftsmanship, you'll appreciate how this library maintains architectural boundaries:
|
|
12
|
+
|
|
13
|
+
**Clear Separation of Concerns:**
|
|
14
|
+
```typescript
|
|
15
|
+
// Server logic stays in controllers (Application Layer)
|
|
16
|
+
@Controller()
|
|
17
|
+
export class UserController {
|
|
18
|
+
constructor(private userService: UserService) {} // Proper DI
|
|
19
|
+
|
|
20
|
+
@Get('/users/:id')
|
|
21
|
+
@Render('views/user-profile')
|
|
22
|
+
async getUserProfile(@Param('id') id: string) {
|
|
23
|
+
const user = await this.userService.findById(id); // Business logic
|
|
24
|
+
return { user }; // Pure data - no rendering concerns
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// View logic stays in React components (Presentation Layer)
|
|
29
|
+
export default function UserProfile({ data }: PageProps<{ user: User }>) {
|
|
30
|
+
return <div>{data.user.name}</div>; // Pure presentation
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**No Server/Client Confusion:**
|
|
35
|
+
- Server code is server code (Controllers, Services, Guards)
|
|
36
|
+
- Client code is client code (React components, hooks)
|
|
37
|
+
- No `'use server'` / `'use client'` directives scattered throughout
|
|
38
|
+
- No mixing of database queries with JSX
|
|
39
|
+
- No hidden boundaries where code "magically" switches execution context
|
|
40
|
+
|
|
41
|
+
**True Dependency Injection:**
|
|
42
|
+
```typescript
|
|
43
|
+
// Use NestJS DI throughout your application
|
|
44
|
+
@Injectable()
|
|
45
|
+
export class ProductService {
|
|
46
|
+
constructor(
|
|
47
|
+
private db: DatabaseService,
|
|
48
|
+
private cache: CacheService,
|
|
49
|
+
) {}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Testable, mockable, follows IoC principle
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Why Not Next.js?
|
|
56
|
+
|
|
57
|
+
Next.js is excellent for many use cases, but if you care about architectural integrity:
|
|
58
|
+
|
|
59
|
+
**Next.js encourages coupling:**
|
|
60
|
+
```tsx
|
|
61
|
+
// ❌ Server and client code mixed in the same file
|
|
62
|
+
export default function Page() {
|
|
63
|
+
const data = await fetch('...').then(r => r.json()); // Server
|
|
64
|
+
const [count, setCount] = useState(0); // Client
|
|
65
|
+
return <button onClick={() => setCount(count + 1)}>{data.title}</button>;
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
- Server and client logic intertwined
|
|
69
|
+
- No dependency injection - just imports and function calls
|
|
70
|
+
- Difficult to test business logic in isolation
|
|
71
|
+
- Routes are files, not proper routing with guards/interceptors
|
|
72
|
+
- Framework-specific patterns instead of universal principles
|
|
73
|
+
|
|
74
|
+
**NestJS SSR maintains boundaries:**
|
|
75
|
+
```tsx
|
|
76
|
+
// ✅ Controller: Server-only, testable, uses DI
|
|
77
|
+
@Controller()
|
|
78
|
+
export class ProductController {
|
|
79
|
+
constructor(private productService: ProductService) {}
|
|
80
|
+
|
|
81
|
+
@Get('/products')
|
|
82
|
+
@UseGuards(AuthGuard) // Proper middleware
|
|
83
|
+
@Render('views/products')
|
|
84
|
+
async list() {
|
|
85
|
+
return { products: await this.productService.findAll() };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ✅ View: Client-only, pure presentation
|
|
90
|
+
export default function Products({ data }: PageProps) {
|
|
91
|
+
const [selected, setSelected] = useState(null);
|
|
92
|
+
return <ProductList products={data.products} onSelect={setSelected} />;
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Performance as a Bonus
|
|
97
|
+
|
|
98
|
+
Following clean architecture doesn't mean sacrificing performance. In our benchmarks:
|
|
99
|
+
|
|
100
|
+
- **NestJS SSR:** 3,050 req/sec (32ms latency)
|
|
101
|
+
- **Next.js:** 2,965 req/sec (33ms latency)
|
|
102
|
+
- **Remix:** 915 req/sec (109ms latency)
|
|
103
|
+
|
|
104
|
+
**Equal performance** with **better architecture.**
|
|
105
|
+
|
|
106
|
+
## Features
|
|
107
|
+
|
|
108
|
+
✅ **Architectural Integrity** - Respects SOLID and Clean Architecture principles
|
|
109
|
+
✅ **Dependency Injection** - Full NestJS DI throughout your application
|
|
110
|
+
✅ **Clear Boundaries** - Server code is server, client code is client
|
|
111
|
+
✅ **Zero Configuration** - Works out of the box with sensible defaults
|
|
112
|
+
✅ **TypeScript First** - Fully typed with excellent IDE support
|
|
113
|
+
✅ **Streaming SSR** - Modern renderToPipeableStream support
|
|
114
|
+
✅ **HMR in Development** - Powered by Vite for instant feedback
|
|
115
|
+
✅ **Production Optimized** - Code splitting, compression, and caching
|
|
116
|
+
✅ **Testable** - Easy to unit test controllers and services separately
|
|
117
|
+
|
|
118
|
+
## Quick Start
|
|
119
|
+
|
|
120
|
+
### Installation
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
npm install @nestjs-ssr/react react react-dom vite
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 1. Configure Vite
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// vite.config.ts
|
|
130
|
+
import { defineConfig } from 'vite';
|
|
131
|
+
import react from '@vitejs/plugin-react';
|
|
132
|
+
import { viewRegistryPlugin } from '@nestjs-ssr/react/vite';
|
|
133
|
+
|
|
134
|
+
export default defineConfig({
|
|
135
|
+
plugins: [react(), viewRegistryPlugin()],
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### 2. Setup NestJS Module
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
// app.module.ts
|
|
143
|
+
import { Module } from '@nestjs/common';
|
|
144
|
+
import { RenderModule } from '@nestjs-ssr/react';
|
|
145
|
+
|
|
146
|
+
@Module({
|
|
147
|
+
imports: [
|
|
148
|
+
RenderModule.register(), // Zero config!
|
|
149
|
+
],
|
|
150
|
+
})
|
|
151
|
+
export class AppModule {}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### 3. Create a View
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
// src/views/home.tsx
|
|
158
|
+
import type { PageProps } from '@nestjs-ssr/react';
|
|
159
|
+
|
|
160
|
+
interface HomeData {
|
|
161
|
+
message: string;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export default function Home({ data }: PageProps<HomeData>) {
|
|
165
|
+
return <h1>{data.message}</h1>;
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### 4. Create a Controller
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
// app.controller.ts
|
|
173
|
+
import { Controller, Get } from '@nestjs/common';
|
|
174
|
+
import { Render } from '@nestjs-ssr/react';
|
|
175
|
+
|
|
176
|
+
@Controller()
|
|
177
|
+
export class AppController {
|
|
178
|
+
@Get()
|
|
179
|
+
@Render('views/home')
|
|
180
|
+
getHome() {
|
|
181
|
+
return { message: 'Hello from NestJS SSR!' };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### 5. Add Entry Files
|
|
187
|
+
|
|
188
|
+
Create these files in `src/view/`:
|
|
189
|
+
|
|
190
|
+
**entry-client.tsx**:
|
|
191
|
+
```typescript
|
|
192
|
+
import { StrictMode } from 'react';
|
|
193
|
+
import { hydrateRoot } from 'react-dom/client';
|
|
194
|
+
import { viewRegistry } from './view-registry.generated';
|
|
195
|
+
|
|
196
|
+
const viewPath = window.__COMPONENT_PATH__;
|
|
197
|
+
const initialProps = window.__INITIAL_STATE__ || {};
|
|
198
|
+
const renderContext = window.__CONTEXT__ || {};
|
|
199
|
+
|
|
200
|
+
const ViewComponent = viewRegistry[viewPath];
|
|
201
|
+
|
|
202
|
+
if (!ViewComponent) {
|
|
203
|
+
throw new Error(`View "${viewPath}" not found in registry`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
hydrateRoot(
|
|
207
|
+
document.getElementById('root')!,
|
|
208
|
+
<StrictMode>
|
|
209
|
+
<ViewComponent data={initialProps} context={renderContext} />
|
|
210
|
+
</StrictMode>
|
|
211
|
+
);
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**entry-server.tsx**:
|
|
215
|
+
```typescript
|
|
216
|
+
import { viewRegistry } from './view-registry.generated';
|
|
217
|
+
|
|
218
|
+
export function render(viewPath: string, props: any, context: any) {
|
|
219
|
+
const ViewComponent = viewRegistry[viewPath];
|
|
220
|
+
|
|
221
|
+
if (!ViewComponent) {
|
|
222
|
+
throw new Error(`View "${viewPath}" not found in registry`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return <ViewComponent data={props} context={context} />;
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### 6. Run
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
npm run dev
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Visit [http://localhost:3000](http://localhost:3000) 🎉
|
|
236
|
+
|
|
237
|
+
## Core Concepts
|
|
238
|
+
|
|
239
|
+
### The `@Render` Decorator
|
|
240
|
+
|
|
241
|
+
The decorator intercepts your controller's response and renders it with React:
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
@Get('/users/:id')
|
|
245
|
+
@Render('users/views/user-profile')
|
|
246
|
+
async getUser(@Param('id') id: string) {
|
|
247
|
+
const user = await this.userService.findOne(id);
|
|
248
|
+
return { user }; // Passed as `data` prop to component
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Type-Safe Props
|
|
253
|
+
|
|
254
|
+
Components receive props with full TypeScript support:
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
import type { PageProps, RenderContext } from '@nestjs-ssr/react';
|
|
258
|
+
|
|
259
|
+
interface UserData {
|
|
260
|
+
user: User;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export default function UserProfile({ data, context }: PageProps<UserData>) {
|
|
264
|
+
return (
|
|
265
|
+
<div>
|
|
266
|
+
<h1>{data.user.name}</h1>
|
|
267
|
+
<p>Requested from: {context.path}</p>
|
|
268
|
+
</div>
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Request Context
|
|
274
|
+
|
|
275
|
+
Every component receives the request context:
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
interface RenderContext {
|
|
279
|
+
url: string;
|
|
280
|
+
path: string;
|
|
281
|
+
query: Record<string, any>;
|
|
282
|
+
params: Record<string, string>;
|
|
283
|
+
userAgent?: string;
|
|
284
|
+
acceptLanguage?: string;
|
|
285
|
+
referer?: string;
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
You can extend it with custom data using TypeScript declaration merging!
|
|
290
|
+
|
|
291
|
+
### React Hooks
|
|
292
|
+
|
|
293
|
+
Access context in any component:
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
import { usePageContext, useParams, useQuery } from '@nestjs-ssr/react';
|
|
297
|
+
|
|
298
|
+
function MyComponent() {
|
|
299
|
+
const context = usePageContext();
|
|
300
|
+
const params = useParams();
|
|
301
|
+
const query = useQuery();
|
|
302
|
+
|
|
303
|
+
return <div>User ID: {params.id}</div>;
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## Configuration
|
|
308
|
+
|
|
309
|
+
### SSR Modes
|
|
310
|
+
|
|
311
|
+
Choose between string mode (simple) or stream mode (faster):
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
RenderModule.register({
|
|
315
|
+
mode: 'stream', // or 'string' (default)
|
|
316
|
+
})
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Custom Error Pages
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
import { ErrorPageCustom } from './error-page';
|
|
323
|
+
|
|
324
|
+
RenderModule.register({
|
|
325
|
+
errorPageDevelopment: ErrorPageCustom,
|
|
326
|
+
errorPageProduction: ErrorPageCustom,
|
|
327
|
+
})
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Environment Variables
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
SSR_MODE=stream # 'string' or 'stream'
|
|
334
|
+
NODE_ENV=production # Enables production optimizations
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Vite Development Setup
|
|
338
|
+
|
|
339
|
+
You have two options for integrating Vite during development:
|
|
340
|
+
|
|
341
|
+
#### Option 1: Simple Setup (Recommended for Getting Started)
|
|
342
|
+
|
|
343
|
+
Use Vite in middleware mode directly within NestJS. This approach is simpler but requires manual page refresh to see changes.
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
// main.ts
|
|
347
|
+
import { createServer as createViteServer } from 'vite';
|
|
348
|
+
import { RenderService } from '@nestjs-ssr/react';
|
|
349
|
+
|
|
350
|
+
async function bootstrap() {
|
|
351
|
+
const app = await NestFactory.create(AppModule);
|
|
352
|
+
const renderService = app.get(RenderService);
|
|
353
|
+
|
|
354
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
355
|
+
const vite = await createViteServer({
|
|
356
|
+
server: { middlewareMode: true },
|
|
357
|
+
appType: 'custom',
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
app.use(vite.middlewares);
|
|
361
|
+
renderService.setViteServer(vite);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
await app.listen(3000);
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
**Pros:**
|
|
369
|
+
- Simple setup (~10 lines of code)
|
|
370
|
+
- Single server on port 3000
|
|
371
|
+
- Auto-restart on file changes
|
|
372
|
+
|
|
373
|
+
**Cons:**
|
|
374
|
+
- Requires manual page refresh to see changes
|
|
375
|
+
- No hot module replacement (HMR)
|
|
376
|
+
|
|
377
|
+
See [`minimal-simple`](../../examples/minimal-simple) example.
|
|
378
|
+
|
|
379
|
+
#### Option 2: Full HMR Setup (Best Developer Experience)
|
|
380
|
+
|
|
381
|
+
Run a separate Vite dev server with proxy middleware for instant hot reloading.
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
// main.ts
|
|
385
|
+
import { createServer as createViteServer } from 'vite';
|
|
386
|
+
import { RenderService } from '@nestjs-ssr/react';
|
|
387
|
+
|
|
388
|
+
async function bootstrap() {
|
|
389
|
+
const app = await NestFactory.create(AppModule);
|
|
390
|
+
const renderService = app.get(RenderService);
|
|
391
|
+
|
|
392
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
393
|
+
// Proxy to external Vite server for HMR
|
|
394
|
+
const { createProxyMiddleware } = await import('http-proxy-middleware');
|
|
395
|
+
const viteProxy = createProxyMiddleware({
|
|
396
|
+
target: 'http://localhost:5173',
|
|
397
|
+
changeOrigin: true,
|
|
398
|
+
ws: true,
|
|
399
|
+
pathFilter: (pathname: string) => {
|
|
400
|
+
return (
|
|
401
|
+
pathname.startsWith('/src/') ||
|
|
402
|
+
pathname.startsWith('/@') ||
|
|
403
|
+
pathname.startsWith('/node_modules/')
|
|
404
|
+
);
|
|
405
|
+
},
|
|
406
|
+
});
|
|
407
|
+
app.use(viteProxy);
|
|
408
|
+
|
|
409
|
+
// Vite instance for SSR
|
|
410
|
+
const vite = await createViteServer({
|
|
411
|
+
server: { middlewareMode: true },
|
|
412
|
+
appType: 'custom',
|
|
413
|
+
});
|
|
414
|
+
renderService.setViteServer(vite);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
await app.listen(3000);
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
**Additional dependencies:**
|
|
422
|
+
```bash
|
|
423
|
+
npm install -D http-proxy-middleware concurrently
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
**package.json scripts:**
|
|
427
|
+
```json
|
|
428
|
+
{
|
|
429
|
+
"scripts": {
|
|
430
|
+
"start:dev": "concurrently \"vite --port 5173\" \"nest start --watch\""
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
**vite.config.js:**
|
|
436
|
+
```javascript
|
|
437
|
+
export default defineConfig({
|
|
438
|
+
server: {
|
|
439
|
+
port: 5173,
|
|
440
|
+
strictPort: true,
|
|
441
|
+
hmr: { port: 5173 }, // Critical for HMR
|
|
442
|
+
},
|
|
443
|
+
});
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
**Pros:**
|
|
447
|
+
- Instant hot module replacement (HMR)
|
|
448
|
+
- Best developer experience for rapid iteration
|
|
449
|
+
- No page refresh needed
|
|
450
|
+
|
|
451
|
+
**Cons:**
|
|
452
|
+
- More complex setup (~40 lines)
|
|
453
|
+
- Requires two servers (NestJS + Vite)
|
|
454
|
+
- Additional dependencies
|
|
455
|
+
|
|
456
|
+
See [`minimal`](../../examples/minimal) example.
|
|
457
|
+
|
|
458
|
+
## Advanced Features
|
|
459
|
+
|
|
460
|
+
### Error Monitoring (Coming in v0.2.0)
|
|
461
|
+
|
|
462
|
+
Error monitoring integration is planned for the next release. It will support integrations with Sentry, Datadog, and custom error reporters.
|
|
463
|
+
|
|
464
|
+
For now, you can use NestJS's built-in exception filters and logging for error handling.
|
|
465
|
+
|
|
466
|
+
### Extending Context
|
|
467
|
+
|
|
468
|
+
Add custom data to the render context:
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
// types/render-context.d.ts
|
|
472
|
+
declare module '@nestjs-ssr/react' {
|
|
473
|
+
interface RenderContext {
|
|
474
|
+
user?: User;
|
|
475
|
+
tenant?: string;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
Then inject it in a custom interceptor.
|
|
481
|
+
|
|
482
|
+
## Examples
|
|
483
|
+
|
|
484
|
+
Choose the example that matches your needs:
|
|
485
|
+
|
|
486
|
+
- **[Minimal Simple](../../examples/minimal-simple/)** - Simplest setup with Vite middleware (no HMR)
|
|
487
|
+
- Perfect for getting started quickly
|
|
488
|
+
- Single server, minimal configuration
|
|
489
|
+
|
|
490
|
+
- **[Minimal](../../examples/minimal/)** - Full HMR setup with dual-server architecture
|
|
491
|
+
- Best developer experience with instant hot reloading
|
|
492
|
+
- Recommended for active development
|
|
493
|
+
|
|
494
|
+
- **[Full-Featured](../../examples/full-featured/)** - Production-ready example
|
|
495
|
+
- Security headers, caching, error handling
|
|
496
|
+
- Streaming SSR with React Suspense
|
|
497
|
+
|
|
498
|
+
## Documentation
|
|
499
|
+
|
|
500
|
+
- [Getting Started](../../docs/getting-started.md)
|
|
501
|
+
- [Why NestJS SSR?](../../docs/why-nestjs-ssr.md)
|
|
502
|
+
- [Architecture](../../docs/ARCHITECTURE.md)
|
|
503
|
+
- [Testing Guide](../../docs/TESTING_STRATEGY.md)
|
|
504
|
+
- [Production Deployment](../../docs/PRODUCTION_RISKS.md)
|
|
505
|
+
|
|
506
|
+
## Philosophy
|
|
507
|
+
|
|
508
|
+
This package is built on two foundational principles:
|
|
509
|
+
|
|
510
|
+
### Clean Architecture & SOLID Principles
|
|
511
|
+
|
|
512
|
+
Following [Uncle Bob's Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) and software craftsmanship principles:
|
|
513
|
+
|
|
514
|
+
**Single Responsibility Principle (SRP):**
|
|
515
|
+
- Controllers handle HTTP routing and orchestration
|
|
516
|
+
- Services contain business logic
|
|
517
|
+
- Views handle presentation
|
|
518
|
+
- Each has one reason to change
|
|
519
|
+
|
|
520
|
+
**Dependency Inversion Principle (DIP):**
|
|
521
|
+
- Controllers depend on service abstractions (interfaces)
|
|
522
|
+
- Services are injected via NestJS DI
|
|
523
|
+
- Views receive data as props (dependency injection via props)
|
|
524
|
+
- No concrete dependencies on frameworks in your business logic
|
|
525
|
+
|
|
526
|
+
**Interface Segregation & Open/Closed:**
|
|
527
|
+
- Use NestJS Guards for authentication/authorization
|
|
528
|
+
- Use Interceptors for cross-cutting concerns
|
|
529
|
+
- Views are pure functions - open for extension, closed for modification
|
|
530
|
+
|
|
531
|
+
**Clear Architectural Boundaries:**
|
|
532
|
+
```
|
|
533
|
+
┌─────────────────────────────────────────┐
|
|
534
|
+
│ Presentation Layer (React Components) │
|
|
535
|
+
│ - Pure presentation logic │
|
|
536
|
+
│ - Client-side interactivity │
|
|
537
|
+
│ - No business logic │
|
|
538
|
+
└─────────────────────────────────────────┘
|
|
539
|
+
↓ Props (Data Flow)
|
|
540
|
+
┌─────────────────────────────────────────┐
|
|
541
|
+
│ Application Layer (Controllers) │
|
|
542
|
+
│ - Request handling │
|
|
543
|
+
│ - Orchestration │
|
|
544
|
+
│ - DTO validation │
|
|
545
|
+
└─────────────────────────────────────────┘
|
|
546
|
+
↓ DI (Dependency Injection)
|
|
547
|
+
┌─────────────────────────────────────────┐
|
|
548
|
+
│ Domain Layer (Services) │
|
|
549
|
+
│ - Business logic │
|
|
550
|
+
│ - Domain models │
|
|
551
|
+
│ - Use cases │
|
|
552
|
+
└─────────────────────────────────────────┘
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
### UnJS Philosophy
|
|
556
|
+
|
|
557
|
+
Following the [UnJS philosophy](https://unjs.io/) for modern JavaScript libraries:
|
|
558
|
+
|
|
559
|
+
1. **Unintrusive** - Integrates seamlessly with existing NestJS apps
|
|
560
|
+
2. **Zero-Config** - Works out of the box with sensible defaults
|
|
561
|
+
3. **Fully Extensible** - Customize everything when needed
|
|
562
|
+
4. **Framework Agnostic** - No opinions on routing, state, or business logic
|
|
563
|
+
5. **TypeScript First** - Excellent type safety and IDE support
|
|
564
|
+
|
|
565
|
+
**The Result:** Clean, maintainable, testable code that scales with your team and product.
|
|
566
|
+
|
|
567
|
+
## Requirements
|
|
568
|
+
|
|
569
|
+
- Node.js 18+
|
|
570
|
+
- NestJS 11+
|
|
571
|
+
- React 19+
|
|
572
|
+
- Vite 6+
|
|
573
|
+
- TypeScript 5+
|
|
574
|
+
|
|
575
|
+
## License
|
|
576
|
+
|
|
577
|
+
MIT © Georgi Alexandrov
|
|
578
|
+
|
|
579
|
+
## Contributing
|
|
580
|
+
|
|
581
|
+
Contributions welcome! Please see [CONTRIBUTING.md](../../CONTRIBUTING.md).
|
|
582
|
+
|
|
583
|
+
## Support
|
|
584
|
+
|
|
585
|
+
- [GitHub Issues](https://github.com/georgialexandrov/nestjs-ssr/issues)
|
|
586
|
+
- [Documentation](../../docs/)
|
|
587
|
+
- [Examples](../../examples/)
|