@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.
@@ -0,0 +1,419 @@
1
+ import { DynamicModule, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
2
+ import { ComponentType } from 'react';
3
+ import { ViteDevServer } from 'vite';
4
+ import { Response } from 'express';
5
+ import { Reflector } from '@nestjs/core';
6
+ import { Observable } from 'rxjs';
7
+ import * as react_jsx_runtime from 'react/jsx-runtime';
8
+
9
+ /**
10
+ * HTML head data for SEO and page metadata
11
+ */
12
+ interface HeadData {
13
+ /** Page title (appears in browser tab and search results) */
14
+ title?: string;
15
+ /** Page description for search engines */
16
+ description?: string;
17
+ /** Page keywords (legacy, less important for modern SEO) */
18
+ keywords?: string;
19
+ /** Canonical URL for duplicate content */
20
+ canonical?: string;
21
+ /** Open Graph title for social media sharing */
22
+ ogTitle?: string;
23
+ /** Open Graph description for social media sharing */
24
+ ogDescription?: string;
25
+ /** Open Graph image URL for social media previews */
26
+ ogImage?: string;
27
+ /** Additional link tags (fonts, icons, preloads, etc.) */
28
+ links?: Array<{
29
+ rel: string;
30
+ href: string;
31
+ as?: string;
32
+ type?: string;
33
+ crossorigin?: string;
34
+ [key: string]: any;
35
+ }>;
36
+ /** Additional meta tags */
37
+ meta?: Array<{
38
+ name?: string;
39
+ property?: string;
40
+ content: string;
41
+ [key: string]: any;
42
+ }>;
43
+ }
44
+ /**
45
+ * Response structure for SSR rendering
46
+ *
47
+ * Can be returned from controllers decorated with @Render.
48
+ * For backwards compatibility, controllers can also return plain objects
49
+ * which will be auto-wrapped as { props: data }.
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * // Simple case - just props (auto-wrapped)
54
+ * @Render('views/home')
55
+ * getHome() {
56
+ * return { message: 'Hello' };
57
+ * // Treated as: { props: { message: 'Hello' } }
58
+ * }
59
+ *
60
+ * // Advanced case - with head data
61
+ * @Render('views/user')
62
+ * getUser(@Param('id') id: string) {
63
+ * const user = await this.userService.findOne(id);
64
+ * return {
65
+ * props: { user },
66
+ * head: {
67
+ * title: `${user.name} - Profile`,
68
+ * description: user.bio,
69
+ * ogImage: user.avatar
70
+ * }
71
+ * };
72
+ * }
73
+ * ```
74
+ */
75
+ interface RenderResponse<T = any> {
76
+ /** Props passed to the React component */
77
+ props: T;
78
+ /** HTML head data (title, meta tags, links) */
79
+ head?: HeadData;
80
+ }
81
+
82
+ /**
83
+ * SSR rendering mode configuration
84
+ */
85
+ type SSRMode = 'string' | 'stream';
86
+ /**
87
+ * Props for development error page component
88
+ */
89
+ interface ErrorPageDevelopmentProps$1 {
90
+ error: Error;
91
+ viewPath: string;
92
+ phase: 'shell' | 'streaming';
93
+ }
94
+ /**
95
+ * Vite development mode configuration
96
+ */
97
+ type ViteMode = 'proxy' | 'embedded';
98
+ /**
99
+ * Vite configuration options
100
+ */
101
+ interface ViteConfig {
102
+ /**
103
+ * Vite mode for development
104
+ * - 'embedded': Vite runs inside NestJS (no HMR, simplest setup) - DEFAULT
105
+ * - 'proxy': External Vite server with HMR support (requires running `vite` separately)
106
+ *
107
+ * @default 'embedded'
108
+ */
109
+ mode?: ViteMode;
110
+ /**
111
+ * Port where external Vite dev server is running (proxy mode only)
112
+ *
113
+ * @default 5173
114
+ */
115
+ port?: number;
116
+ }
117
+ /**
118
+ * Configuration options for the render module
119
+ */
120
+ interface RenderConfig {
121
+ /**
122
+ * SSR rendering mode
123
+ * - 'string': Traditional renderToString (simple, proven, easier debugging)
124
+ * - 'stream': Modern renderToPipeableStream (better performance, progressive rendering)
125
+ *
126
+ * @default 'string'
127
+ */
128
+ mode?: SSRMode;
129
+ /**
130
+ * Timeout in milliseconds for SSR rendering.
131
+ * If rendering takes longer than this, the request will be aborted.
132
+ *
133
+ * @default 10000 (10 seconds)
134
+ */
135
+ timeout?: number;
136
+ /**
137
+ * Vite configuration for development
138
+ *
139
+ * @example
140
+ * ```typescript
141
+ * // Zero config - embedded mode by default (simplest)
142
+ * @Module({
143
+ * imports: [RenderModule],
144
+ * })
145
+ *
146
+ * // Proxy mode - external Vite with HMR
147
+ * @Module({
148
+ * imports: [
149
+ * RenderModule.register({
150
+ * vite: { mode: 'proxy', port: 5173 }
151
+ * })
152
+ * ],
153
+ * })
154
+ * ```
155
+ */
156
+ vite?: ViteConfig;
157
+ /**
158
+ * Custom error page component for development environment
159
+ * Receives error details and renders custom error UI
160
+ *
161
+ * @default ErrorPageDevelopment from '@shared/render/error-pages'
162
+ */
163
+ errorPageDevelopment?: ComponentType<ErrorPageDevelopmentProps$1>;
164
+ /**
165
+ * Custom error page component for production environment
166
+ * Renders generic error without sensitive details
167
+ *
168
+ * @default ErrorPageProduction from '@shared/render/error-pages'
169
+ */
170
+ errorPageProduction?: ComponentType;
171
+ /**
172
+ * Default head data for all pages
173
+ * Can be overridden per-page by returning head in controller
174
+ *
175
+ * For dynamic default head (e.g., from database), use registerAsync
176
+ *
177
+ * @example
178
+ * ```typescript
179
+ * RenderModule.register({
180
+ * defaultHead: {
181
+ * title: 'My App',
182
+ * description: 'Default description',
183
+ * links: [{ rel: 'icon', href: '/favicon.ico' }]
184
+ * }
185
+ * })
186
+ * ```
187
+ */
188
+ defaultHead?: HeadData;
189
+ }
190
+ /**
191
+ * Template parts for streaming SSR
192
+ * Template is split into parts that are written around the React stream
193
+ */
194
+ interface TemplateParts {
195
+ /** HTML start through <body> tag */
196
+ htmlStart: string;
197
+ /** Opening <div id="root"> tag */
198
+ rootStart: string;
199
+ /** Closing </div> tag for root */
200
+ rootEnd: string;
201
+ /** Closing </body></html> tags */
202
+ htmlEnd: string;
203
+ }
204
+
205
+ declare class RenderModule {
206
+ /**
207
+ * Register the render module with optional configuration
208
+ *
209
+ * @param config - Optional render configuration
210
+ * @returns Dynamic module
211
+ *
212
+ * @example
213
+ * ```ts
214
+ * // Zero config - embedded mode by default (simplest)
215
+ * @Module({
216
+ * imports: [RenderModule],
217
+ * })
218
+ *
219
+ * // Enable HMR with proxy mode
220
+ * @Module({
221
+ * imports: [
222
+ * RenderModule.register({
223
+ * vite: { mode: 'proxy', port: 5173 }
224
+ * })
225
+ * ],
226
+ * })
227
+ *
228
+ * // Enable streaming SSR
229
+ * RenderModule.register({ mode: 'stream' })
230
+ *
231
+ * // Custom error pages
232
+ * RenderModule.register({
233
+ * mode: 'stream',
234
+ * errorPageDevelopment: MyCustomDevErrorPage,
235
+ * errorPageProduction: MyCustomProdErrorPage,
236
+ * })
237
+ * ```
238
+ */
239
+ static register(config?: RenderConfig): DynamicModule;
240
+ /**
241
+ * Register the render module asynchronously with dynamic configuration
242
+ *
243
+ * Use this when you need to inject services (e.g., load config from database)
244
+ *
245
+ * @param options - Async configuration options
246
+ * @returns Dynamic module
247
+ *
248
+ * @example
249
+ * ```ts
250
+ * // Load default head from database
251
+ * RenderModule.registerAsync({
252
+ * imports: [TenantModule],
253
+ * inject: [TenantRepository],
254
+ * useFactory: async (tenantRepo: TenantRepository) => {
255
+ * const tenant = await tenantRepo.findDefaultTenant();
256
+ * return {
257
+ * defaultHead: {
258
+ * title: tenant.appName,
259
+ * description: tenant.description,
260
+ * links: [
261
+ * { rel: 'icon', href: tenant.favicon }
262
+ * ]
263
+ * }
264
+ * };
265
+ * }
266
+ * })
267
+ * ```
268
+ */
269
+ static registerAsync(options: {
270
+ imports?: any[];
271
+ inject?: any[];
272
+ useFactory: (...args: any[]) => Promise<RenderConfig> | RenderConfig;
273
+ }): DynamicModule;
274
+ }
275
+
276
+ /**
277
+ * Service for parsing HTML templates and building inline scripts for SSR
278
+ */
279
+ declare class TemplateParserService {
280
+ private readonly headTagRenderers;
281
+ /**
282
+ * Parse HTML template into parts for streaming SSR
283
+ *
284
+ * Splits the template at strategic injection points:
285
+ * - Before root div: Shell HTML (head, body start)
286
+ * - Root div start
287
+ * - Root div end
288
+ * - After root: Scripts and closing tags
289
+ */
290
+ parseTemplate(html: string): TemplateParts;
291
+ /**
292
+ * Build inline script that provides initial state to the client
293
+ *
294
+ * Safely serializes data using serialize-javascript to avoid XSS vulnerabilities.
295
+ * This library handles all edge cases including escaping dangerous characters,
296
+ * functions, dates, regexes, and prevents prototype pollution.
297
+ */
298
+ buildInlineScripts(data: any, context: any, componentPath: string): string;
299
+ /**
300
+ * Get client script tag for hydration
301
+ *
302
+ * In development: Direct module import with Vite HMR
303
+ * In production: Hashed filename from manifest
304
+ */
305
+ getClientScriptTag(isDevelopment: boolean, manifest?: any): string;
306
+ /**
307
+ * Get stylesheet link tags
308
+ *
309
+ * In development: Direct link to source CSS file
310
+ * In production: Hashed CSS files from manifest
311
+ */
312
+ getStylesheetTags(isDevelopment: boolean, manifest?: any): string;
313
+ /**
314
+ * Build HTML head tags from HeadData
315
+ *
316
+ * Generates title, meta tags, and link tags for SEO and page metadata.
317
+ * Safely escapes content using escape-html to prevent XSS.
318
+ */
319
+ buildHeadTags(head?: HeadData): string;
320
+ /**
321
+ * Build an HTML tag from an object of attributes
322
+ */
323
+ private buildTag;
324
+ }
325
+
326
+ /**
327
+ * Error handling strategies for streaming SSR
328
+ *
329
+ * Streaming has different error phases:
330
+ * 1. Shell errors: Before any content sent (can send 500)
331
+ * 2. Stream errors: After headers sent (can only log)
332
+ * 3. Client errors: Handled by ErrorBoundary
333
+ */
334
+ declare class StreamingErrorHandler {
335
+ private readonly errorPageDevelopment?;
336
+ private readonly errorPageProduction?;
337
+ private readonly logger;
338
+ constructor(errorPageDevelopment?: ComponentType<ErrorPageDevelopmentProps$1> | undefined, errorPageProduction?: ComponentType | undefined);
339
+ /**
340
+ * Handle error that occurred before shell was ready
341
+ * Can still set HTTP status code and send error page
342
+ */
343
+ handleShellError(error: Error, res: Response, viewPath: string, isDevelopment: boolean): void;
344
+ /**
345
+ * Handle error that occurred during streaming
346
+ * Headers already sent, can only log the error
347
+ */
348
+ handleStreamError(error: Error, viewPath: string): void;
349
+ /**
350
+ * Render development error page using React component
351
+ */
352
+ private renderDevelopmentErrorPage;
353
+ /**
354
+ * Render production error page using React component
355
+ */
356
+ private renderProductionErrorPage;
357
+ }
358
+
359
+ declare class RenderService {
360
+ private readonly templateParser;
361
+ private readonly streamingErrorHandler;
362
+ private readonly defaultHead?;
363
+ private readonly logger;
364
+ private vite;
365
+ private template;
366
+ private manifest;
367
+ private serverManifest;
368
+ private isDevelopment;
369
+ private ssrMode;
370
+ constructor(templateParser: TemplateParserService, streamingErrorHandler: StreamingErrorHandler, ssrMode?: SSRMode, defaultHead?: HeadData | undefined);
371
+ setViteServer(vite: ViteDevServer): void;
372
+ /**
373
+ * Main render method that routes to string or stream mode
374
+ */
375
+ render(viewPath: string, data?: any, res?: Response, head?: HeadData): Promise<string | void>;
376
+ /**
377
+ * Merge default head with page-specific head
378
+ * Page-specific head values override defaults
379
+ */
380
+ private mergeHead;
381
+ /**
382
+ * Traditional string-based SSR using renderToString
383
+ */
384
+ private renderToString;
385
+ /**
386
+ * Modern streaming SSR using renderToPipeableStream
387
+ */
388
+ private renderToStream;
389
+ }
390
+
391
+ declare class RenderInterceptor implements NestInterceptor {
392
+ private reflector;
393
+ private renderService;
394
+ constructor(reflector: Reflector, renderService: RenderService);
395
+ intercept(context: ExecutionContext, next: CallHandler): Observable<any>;
396
+ }
397
+
398
+ interface ErrorPageDevelopmentProps {
399
+ error: Error;
400
+ viewPath: string;
401
+ phase: 'shell' | 'streaming';
402
+ }
403
+ /**
404
+ * Default development error page component
405
+ *
406
+ * Shows detailed error information with stack trace
407
+ * App developers can override this by providing their own component
408
+ */
409
+ declare function ErrorPageDevelopment({ error, viewPath, phase, }: ErrorPageDevelopmentProps): react_jsx_runtime.JSX.Element;
410
+
411
+ /**
412
+ * Default production error page component
413
+ *
414
+ * Shows generic error message without sensitive details
415
+ * App developers can override this by providing their own component
416
+ */
417
+ declare function ErrorPageProduction(): react_jsx_runtime.JSX.Element;
418
+
419
+ export { ErrorPageDevelopment as E, type HeadData as H, RenderModule as R, StreamingErrorHandler as S, TemplateParserService as T, RenderService as a, RenderInterceptor as b, type RenderConfig as c, type SSRMode as d, type RenderResponse as e, ErrorPageProduction as f };