@nestjs-ssr/react 0.1.6 → 0.1.8

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
@@ -12,23 +12,29 @@ If you value [Uncle Bob's Clean Architecture](https://blog.cleancoder.com/uncle-
12
12
 
13
13
  **Clear Separation of Concerns:**
14
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
+
15
24
  // Server logic stays in controllers (Application Layer)
25
+ import UserProfile from './views/user-profile';
26
+
16
27
  @Controller()
17
28
  export class UserController {
18
29
  constructor(private userService: UserService) {} // Proper DI
19
30
 
20
31
  @Get('/users/:id')
21
- @Render('views/user-profile')
32
+ @Render(UserProfile) // Type-safe! Cmd+Click to navigate
22
33
  async getUserProfile(@Param('id') id: string) {
23
34
  const user = await this.userService.findById(id); // Business logic
24
- return { user }; // Pure data - no rendering concerns
35
+ return { user }; // TypeScript validates this matches UserProfileProps
25
36
  }
26
37
  }
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
38
  ```
33
39
 
34
40
  **No Server/Client Confusion:**
@@ -73,24 +79,30 @@ export default function Page() {
73
79
 
74
80
  **NestJS SSR maintains boundaries:**
75
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
+
76
92
  // ✅ Controller: Server-only, testable, uses DI
93
+ import Products from './views/products';
94
+
77
95
  @Controller()
78
96
  export class ProductController {
79
97
  constructor(private productService: ProductService) {}
80
98
 
81
99
  @Get('/products')
82
100
  @UseGuards(AuthGuard) // Proper middleware
83
- @Render('views/products')
101
+ @Render(Products) // Type-safe component reference
84
102
  async list() {
85
103
  return { products: await this.productService.findAll() };
86
104
  }
87
105
  }
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
106
  ```
95
107
 
96
108
  ### Performance as a Bonus
@@ -105,6 +117,8 @@ Following clean architecture doesn't mean sacrificing performance. In our benchm
105
117
 
106
118
  ## Features
107
119
 
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
108
122
  ✅ **Architectural Integrity** - Respects SOLID and Clean Architecture principles
109
123
  ✅ **Dependency Injection** - Full NestJS DI throughout your application
110
124
  ✅ **Clear Boundaries** - Server code is server, client code is client
@@ -129,10 +143,15 @@ npm install @nestjs-ssr/react react react-dom vite
129
143
  // vite.config.ts
130
144
  import { defineConfig } from 'vite';
131
145
  import react from '@vitejs/plugin-react';
132
- import { viewRegistryPlugin } from '@nestjs-ssr/react/vite';
146
+ import { resolve } from 'path';
133
147
 
134
148
  export default defineConfig({
135
- plugins: [react(), viewRegistryPlugin()],
149
+ plugins: [react()],
150
+ resolve: {
151
+ alias: {
152
+ '@': resolve(__dirname, 'src'),
153
+ },
154
+ },
136
155
  });
137
156
  ```
138
157
 
@@ -151,18 +170,18 @@ import { RenderModule } from '@nestjs-ssr/react';
151
170
  export class AppModule {}
152
171
  ```
153
172
 
154
- ### 3. Create a View
173
+ ### 3. Create a View Component
155
174
 
156
175
  ```typescript
157
176
  // src/views/home.tsx
158
177
  import type { PageProps } from '@nestjs-ssr/react';
159
178
 
160
- interface HomeData {
179
+ export interface HomeProps {
161
180
  message: string;
162
181
  }
163
182
 
164
- export default function Home({ data }: PageProps<HomeData>) {
165
- return <h1>{data.message}</h1>;
183
+ export default function Home(props: PageProps<HomeProps>) {
184
+ return <h1>{props.message}</h1>;
166
185
  }
167
186
  ```
168
187
 
@@ -172,61 +191,21 @@ export default function Home({ data }: PageProps<HomeData>) {
172
191
  // app.controller.ts
173
192
  import { Controller, Get } from '@nestjs/common';
174
193
  import { Render } from '@nestjs-ssr/react';
194
+ import Home from './views/home';
175
195
 
176
196
  @Controller()
177
197
  export class AppController {
178
198
  @Get()
179
- @Render('views/home')
199
+ @Render(Home) // Type-safe! Cmd+Click to navigate to view
180
200
  getHome() {
181
201
  return { message: 'Hello from NestJS SSR!' };
182
202
  }
183
203
  }
184
204
  ```
185
205
 
186
- ### 5. Add Entry Files
206
+ That's it! No manual view registry or entry files needed - everything is handled automatically.
187
207
 
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
208
+ ### 5. Run
230
209
 
231
210
  ```bash
232
211
  npm run dev
@@ -238,38 +217,46 @@ Visit [http://localhost:3000](http://localhost:3000) 🎉
238
217
 
239
218
  ### The `@Render` Decorator
240
219
 
241
- The decorator intercepts your controller's response and renders it with React:
220
+ The decorator takes a React component and automatically validates that your controller returns the correct props:
242
221
 
243
222
  ```typescript
223
+ import UserProfile from './views/user-profile';
224
+
244
225
  @Get('/users/:id')
245
- @Render('users/views/user-profile')
226
+ @Render(UserProfile) // Type-safe! Cmd+Click to navigate
246
227
  async getUser(@Param('id') id: string) {
247
228
  const user = await this.userService.findOne(id);
248
- return { user }; // Passed as `data` prop to component
229
+ return { user }; // TypeScript validates this matches component props
249
230
  }
250
231
  ```
251
232
 
252
233
  ### Type-Safe Props
253
234
 
254
- Components receive props with full TypeScript support:
235
+ Components receive props with full TypeScript support and validation:
255
236
 
256
237
  ```typescript
257
- import type { PageProps, RenderContext } from '@nestjs-ssr/react';
238
+ import type { PageProps } from '@nestjs-ssr/react';
258
239
 
259
- interface UserData {
240
+ export interface UserProfileProps {
260
241
  user: User;
261
242
  }
262
243
 
263
- export default function UserProfile({ data, context }: PageProps<UserData>) {
244
+ export default function UserProfile(props: PageProps<UserProfileProps>) {
264
245
  return (
265
246
  <div>
266
- <h1>{data.user.name}</h1>
267
- <p>Requested from: {context.path}</p>
247
+ <h1>{props.user.name}</h1>
248
+ <p>Requested from: {props.context.path}</p>
268
249
  </div>
269
250
  );
270
251
  }
271
252
  ```
272
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
259
+
273
260
  ### Request Context
274
261
 
275
262
  Every component receives the request context:
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node