@nestjs-ssr/react 0.1.7 → 0.1.9

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