@nestjs-ssr/react 0.1.11 → 0.2.0
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 +55 -84
- package/dist/cli/init.js +2 -2
- package/dist/cli/init.mjs +2 -2
- package/dist/{index-BiaVDe9J.d.mts → index-C5Knql-9.d.mts} +124 -8
- package/dist/{index-BiaVDe9J.d.ts → index-C5Knql-9.d.ts} +124 -8
- package/dist/index.d.mts +377 -69
- package/dist/index.d.ts +377 -69
- package/dist/index.js +460 -89
- package/dist/index.mjs +459 -85
- package/dist/render/index.d.mts +1 -1
- package/dist/render/index.d.ts +1 -1
- package/dist/render/index.js +253 -64
- package/dist/render/index.mjs +253 -63
- package/dist/templates/entry-client.tsx +80 -13
- package/dist/templates/entry-server.tsx +33 -2
- package/dist/templates/index.html +0 -3
- package/etc/react.api.md +262 -0
- package/package.json +28 -7
- package/src/global.d.ts +1 -1
- package/src/templates/entry-client.tsx +80 -13
- package/src/templates/entry-server.tsx +33 -2
- package/src/templates/index.html +0 -3
package/README.md
CHANGED
|
@@ -1,119 +1,90 @@
|
|
|
1
|
-
#
|
|
1
|
+
# NestJS SSR
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@nestjs-ssr/react)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
> **⚠️ Preview Release**
|
|
7
|
+
> This package is currently in active development. The API may change between minor versions. Production use is not recommended yet.
|
|
6
8
|
|
|
7
|
-
-
|
|
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
|
|
9
|
+
Server-rendered React for NestJS. Controllers return data, components render it.
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
Clean Architecture: layers separated, dependencies inward, business logic framework-agnostic.
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
npm install @nestjs-ssr/react
|
|
17
|
-
npx nestjs-ssr # Set up your project automatically
|
|
18
|
-
```
|
|
13
|
+
## When To Use
|
|
19
14
|
|
|
20
|
-
|
|
15
|
+
**Use this when:**
|
|
21
16
|
|
|
22
|
-
|
|
17
|
+
- You have NestJS and want React instead of Handlebars/EJS
|
|
18
|
+
- Testable layers matter more than file-based routing
|
|
19
|
+
- You want feature modules (controller + service + view together)
|
|
23
20
|
|
|
24
|
-
**
|
|
21
|
+
**Use Next.js when:**
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
- You're starting fresh without NestJS
|
|
24
|
+
- You want the React ecosystem's defaults
|
|
25
|
+
- File-based routing fits your mental model
|
|
29
26
|
|
|
30
|
-
|
|
31
|
-
imports: [RenderModule],
|
|
32
|
-
})
|
|
33
|
-
export class AppModule {}
|
|
34
|
-
```
|
|
27
|
+
## Quick Start
|
|
35
28
|
|
|
36
|
-
|
|
29
|
+
```bash
|
|
30
|
+
npx nestjs-ssr init
|
|
31
|
+
```
|
|
37
32
|
|
|
38
33
|
```typescript
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
message: string;
|
|
34
|
+
@Get(':id')
|
|
35
|
+
@Render(ProductDetail)
|
|
36
|
+
async getProduct(@Param('id') id: string) {
|
|
37
|
+
return { product: await this.productService.findById(id) };
|
|
44
38
|
}
|
|
39
|
+
```
|
|
45
40
|
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
```tsx
|
|
42
|
+
export default function ProductDetail({
|
|
43
|
+
data,
|
|
44
|
+
}: PageProps<{ product: Product }>) {
|
|
45
|
+
return <h1>{data.product.name}</h1>;
|
|
48
46
|
}
|
|
49
47
|
```
|
|
50
48
|
|
|
51
|
-
|
|
49
|
+
Type mismatch = build fails.
|
|
50
|
+
|
|
51
|
+
## Test in Isolation
|
|
52
52
|
|
|
53
53
|
```typescript
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
@Controller()
|
|
60
|
-
export class AppController {
|
|
61
|
-
@Get()
|
|
62
|
-
@Render(Home)
|
|
63
|
-
getHome() {
|
|
64
|
-
return { message: 'Hello World' }; // TypeScript validates this!
|
|
65
|
-
}
|
|
66
|
-
}
|
|
54
|
+
// Controller: no React
|
|
55
|
+
expect(await controller.getProduct('123')).toEqual({ product: { id: '123' } });
|
|
56
|
+
|
|
57
|
+
// Component: no NestJS
|
|
58
|
+
render(<ProductDetail data={{ product: mockProduct }} />);
|
|
67
59
|
```
|
|
68
60
|
|
|
69
|
-
|
|
61
|
+
## Features
|
|
70
62
|
|
|
71
|
-
|
|
63
|
+
**Rendering:**
|
|
72
64
|
|
|
73
|
-
|
|
65
|
+
- Type-safe data flow from controller to component
|
|
66
|
+
- Hierarchical layouts (module → controller → method)
|
|
67
|
+
- Head tags via decorators (title, meta, OG, JSON-LD)
|
|
74
68
|
|
|
75
|
-
|
|
76
|
-
import { usePageContext, useParams, useQuery } from '@nestjs-ssr/react';
|
|
69
|
+
**Request Context:**
|
|
77
70
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const params = useParams(); // Route params
|
|
81
|
-
const query = useQuery(); // Query string
|
|
82
|
-
return <div>User ID: {params.id}</div>;
|
|
83
|
-
}
|
|
84
|
-
```
|
|
71
|
+
- Hooks: params, query, headers, session, user agent
|
|
72
|
+
- Whitelist what reaches the client
|
|
85
73
|
|
|
86
|
-
|
|
74
|
+
**Development:**
|
|
87
75
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
);
|
|
101
|
-
}
|
|
102
|
-
```
|
|
76
|
+
- Integrated mode: one process, full refresh
|
|
77
|
+
- Proxy mode: separate Vite, true HMR
|
|
103
78
|
|
|
104
|
-
##
|
|
79
|
+
## Docs
|
|
105
80
|
|
|
106
|
-
|
|
107
|
-
- [Examples](https://github.com/georgialexandrov/nestjs-ssr/tree/main/examples)
|
|
108
|
-
- [GitHub](https://github.com/georgialexandrov/nestjs-ssr)
|
|
81
|
+
[Full documentation →](https://georgialexandrov.github.io/nest-ssr/)
|
|
109
82
|
|
|
110
|
-
##
|
|
83
|
+
## Examples
|
|
111
84
|
|
|
112
|
-
-
|
|
113
|
-
-
|
|
114
|
-
- React 19+
|
|
115
|
-
- TypeScript 5+
|
|
85
|
+
**[Minimal](./examples/minimal/)** - Simplest setup with integrated Vite mode
|
|
86
|
+
**[Minimal HMR](./examples/minimal-hmr/)** - Dual-server architecture for full HMR
|
|
116
87
|
|
|
117
88
|
## License
|
|
118
89
|
|
|
119
|
-
MIT
|
|
90
|
+
MIT
|
package/dist/cli/init.js
CHANGED
|
@@ -269,9 +269,9 @@ export default defineConfig({
|
|
|
269
269
|
if (!args["skip-install"]) {
|
|
270
270
|
consola.consola.start("Checking dependencies...");
|
|
271
271
|
const requiredDeps = {
|
|
272
|
-
|
|
272
|
+
react: "^19.0.0",
|
|
273
273
|
"react-dom": "^19.0.0",
|
|
274
|
-
|
|
274
|
+
vite: "^7.0.0",
|
|
275
275
|
"@vitejs/plugin-react": "^4.0.0"
|
|
276
276
|
};
|
|
277
277
|
const missingDeps = [];
|
package/dist/cli/init.mjs
CHANGED
|
@@ -266,9 +266,9 @@ export default defineConfig({
|
|
|
266
266
|
if (!args["skip-install"]) {
|
|
267
267
|
consola.start("Checking dependencies...");
|
|
268
268
|
const requiredDeps = {
|
|
269
|
-
|
|
269
|
+
react: "^19.0.0",
|
|
270
270
|
"react-dom": "^19.0.0",
|
|
271
|
-
|
|
271
|
+
vite: "^7.0.0",
|
|
272
272
|
"@vitejs/plugin-react": "^4.0.0"
|
|
273
273
|
};
|
|
274
274
|
const missingDeps = [];
|
|
@@ -40,6 +40,21 @@ interface HeadData {
|
|
|
40
40
|
content: string;
|
|
41
41
|
[key: string]: any;
|
|
42
42
|
}>;
|
|
43
|
+
/** Script tags for analytics, tracking, etc. */
|
|
44
|
+
scripts?: Array<{
|
|
45
|
+
src?: string;
|
|
46
|
+
async?: boolean;
|
|
47
|
+
defer?: boolean;
|
|
48
|
+
type?: string;
|
|
49
|
+
innerHTML?: string;
|
|
50
|
+
[key: string]: any;
|
|
51
|
+
}>;
|
|
52
|
+
/** JSON-LD structured data for search engines */
|
|
53
|
+
jsonLd?: Array<Record<string, any>>;
|
|
54
|
+
/** Attributes to add to <html> tag (e.g., lang, dir) */
|
|
55
|
+
htmlAttributes?: Record<string, string>;
|
|
56
|
+
/** Attributes to add to <body> tag (e.g., class, data-theme) */
|
|
57
|
+
bodyAttributes?: Record<string, string>;
|
|
43
58
|
}
|
|
44
59
|
/**
|
|
45
60
|
* Response structure for SSR rendering
|
|
@@ -57,12 +72,16 @@ interface HeadData {
|
|
|
57
72
|
* // Treated as: { props: { message: 'Hello' } }
|
|
58
73
|
* }
|
|
59
74
|
*
|
|
60
|
-
* // Advanced case - with head data
|
|
75
|
+
* // Advanced case - with head data and layout props
|
|
61
76
|
* @Render('views/user')
|
|
62
77
|
* getUser(@Param('id') id: string) {
|
|
63
78
|
* const user = await this.userService.findOne(id);
|
|
64
79
|
* return {
|
|
65
80
|
* props: { user },
|
|
81
|
+
* layoutProps: {
|
|
82
|
+
* title: user.name,
|
|
83
|
+
* subtitle: 'User Profile'
|
|
84
|
+
* },
|
|
66
85
|
* head: {
|
|
67
86
|
* title: `${user.name} - Profile`,
|
|
68
87
|
* description: user.bio,
|
|
@@ -77,6 +96,17 @@ interface RenderResponse<T = any> {
|
|
|
77
96
|
props: T;
|
|
78
97
|
/** HTML head data (title, meta tags, links) */
|
|
79
98
|
head?: HeadData;
|
|
99
|
+
/**
|
|
100
|
+
* Props passed to layout components (dynamic, per-request)
|
|
101
|
+
*
|
|
102
|
+
* These props are merged with static layout props from decorators:
|
|
103
|
+
* - Static props from @Layout decorator (controller level)
|
|
104
|
+
* - Static props from @Render decorator (method level)
|
|
105
|
+
* - Dynamic props from this field (highest priority)
|
|
106
|
+
*
|
|
107
|
+
* All layout components in the hierarchy receive the merged props.
|
|
108
|
+
*/
|
|
109
|
+
layoutProps?: Record<string, any>;
|
|
80
110
|
}
|
|
81
111
|
|
|
82
112
|
/**
|
|
@@ -154,6 +184,32 @@ interface RenderConfig {
|
|
|
154
184
|
* ```
|
|
155
185
|
*/
|
|
156
186
|
vite?: ViteConfig;
|
|
187
|
+
/**
|
|
188
|
+
* Custom HTML template for SSR
|
|
189
|
+
* Provide either a file path or template string
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```typescript
|
|
193
|
+
* // File path (absolute or relative to cwd)
|
|
194
|
+
* RenderModule.register({
|
|
195
|
+
* template: './src/views/custom-template.html'
|
|
196
|
+
* })
|
|
197
|
+
*
|
|
198
|
+
* // Template string
|
|
199
|
+
* RenderModule.register({
|
|
200
|
+
* template: `<!DOCTYPE html>
|
|
201
|
+
* <html>
|
|
202
|
+
* <head><!--styles--></head>
|
|
203
|
+
* <body>
|
|
204
|
+
* <div id="root"><!--app-html--></div>
|
|
205
|
+
* <!--initial-state-->
|
|
206
|
+
* <!--client-scripts-->
|
|
207
|
+
* </body>
|
|
208
|
+
* </html>`
|
|
209
|
+
* })
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
template?: string;
|
|
157
213
|
/**
|
|
158
214
|
* Custom error page component for development environment
|
|
159
215
|
* Receives error details and renders custom error UI
|
|
@@ -186,6 +242,41 @@ interface RenderConfig {
|
|
|
186
242
|
* ```
|
|
187
243
|
*/
|
|
188
244
|
defaultHead?: HeadData;
|
|
245
|
+
/**
|
|
246
|
+
* HTTP headers to pass to client
|
|
247
|
+
* By default, no headers are passed for security
|
|
248
|
+
* Use this to opt-in to specific headers that are safe to expose
|
|
249
|
+
*
|
|
250
|
+
* Common safe headers: user-agent, accept-language, referer
|
|
251
|
+
* Security warning: Never include sensitive headers like authorization, cookie, etc.
|
|
252
|
+
*
|
|
253
|
+
* @default []
|
|
254
|
+
*
|
|
255
|
+
* @example
|
|
256
|
+
* ```typescript
|
|
257
|
+
* RenderModule.register({
|
|
258
|
+
* allowedHeaders: ['user-agent', 'accept-language', 'x-tenant-id', 'x-api-version']
|
|
259
|
+
* })
|
|
260
|
+
* ```
|
|
261
|
+
*/
|
|
262
|
+
allowedHeaders?: string[];
|
|
263
|
+
/**
|
|
264
|
+
* Cookie names to pass to client
|
|
265
|
+
* By default, no cookies are passed to client for security
|
|
266
|
+
* Use this to opt-in to specific cookies that are safe to expose
|
|
267
|
+
*
|
|
268
|
+
* Security warning: Never include sensitive cookies like session tokens, auth cookies, etc.
|
|
269
|
+
*
|
|
270
|
+
* @default []
|
|
271
|
+
*
|
|
272
|
+
* @example
|
|
273
|
+
* ```typescript
|
|
274
|
+
* RenderModule.register({
|
|
275
|
+
* allowedCookies: ['theme', 'locale', 'consent']
|
|
276
|
+
* })
|
|
277
|
+
* ```
|
|
278
|
+
*/
|
|
279
|
+
allowedCookies?: string[];
|
|
189
280
|
}
|
|
190
281
|
/**
|
|
191
282
|
* Template parts for streaming SSR
|
|
@@ -291,11 +382,14 @@ declare class TemplateParserService {
|
|
|
291
382
|
/**
|
|
292
383
|
* Build inline script that provides initial state to the client
|
|
293
384
|
*
|
|
294
|
-
* Safely serializes data using
|
|
295
|
-
*
|
|
296
|
-
*
|
|
385
|
+
* Safely serializes data using devalue to avoid XSS vulnerabilities.
|
|
386
|
+
* Devalue is designed specifically for SSR, handling complex types safely
|
|
387
|
+
* while being faster and more secure than alternatives.
|
|
297
388
|
*/
|
|
298
|
-
buildInlineScripts(data: any, context: any, componentName: string
|
|
389
|
+
buildInlineScripts(data: any, context: any, componentName: string, layouts?: Array<{
|
|
390
|
+
layout: any;
|
|
391
|
+
props?: any;
|
|
392
|
+
}>): string;
|
|
299
393
|
/**
|
|
300
394
|
* Get client script tag for hydration
|
|
301
395
|
*
|
|
@@ -368,8 +462,18 @@ declare class RenderService {
|
|
|
368
462
|
private isDevelopment;
|
|
369
463
|
private ssrMode;
|
|
370
464
|
private readonly entryServerPath;
|
|
371
|
-
|
|
465
|
+
private rootLayout;
|
|
466
|
+
private rootLayoutChecked;
|
|
467
|
+
constructor(templateParser: TemplateParserService, streamingErrorHandler: StreamingErrorHandler, ssrMode?: SSRMode, defaultHead?: HeadData | undefined, customTemplate?: string);
|
|
372
468
|
setViteServer(vite: ViteDevServer): void;
|
|
469
|
+
/**
|
|
470
|
+
* Get the root layout component if it exists
|
|
471
|
+
* Auto-discovers layout files at conventional paths:
|
|
472
|
+
* - src/views/layout.tsx
|
|
473
|
+
* - src/views/layout/index.tsx
|
|
474
|
+
* - src/views/_layout.tsx
|
|
475
|
+
*/
|
|
476
|
+
getRootLayout(): Promise<any | null>;
|
|
373
477
|
/**
|
|
374
478
|
* Main render method that routes to string or stream mode
|
|
375
479
|
*/
|
|
@@ -392,7 +496,19 @@ declare class RenderService {
|
|
|
392
496
|
declare class RenderInterceptor implements NestInterceptor {
|
|
393
497
|
private reflector;
|
|
394
498
|
private renderService;
|
|
395
|
-
|
|
499
|
+
private allowedHeaders?;
|
|
500
|
+
private allowedCookies?;
|
|
501
|
+
constructor(reflector: Reflector, renderService: RenderService, allowedHeaders?: string[] | undefined, allowedCookies?: string[] | undefined);
|
|
502
|
+
/**
|
|
503
|
+
* Resolve the layout hierarchy for a given route
|
|
504
|
+
* Hierarchy: Root Layout → Controller Layout → Method Layout → Page
|
|
505
|
+
*
|
|
506
|
+
* Props are merged in priority order:
|
|
507
|
+
* 1. Static props from @Layout decorator (base)
|
|
508
|
+
* 2. Static props from @Render decorator (override)
|
|
509
|
+
* 3. Dynamic props from controller return (final override)
|
|
510
|
+
*/
|
|
511
|
+
private resolveLayoutChain;
|
|
396
512
|
intercept(context: ExecutionContext, next: CallHandler): Observable<any>;
|
|
397
513
|
}
|
|
398
514
|
|
|
@@ -417,4 +533,4 @@ declare function ErrorPageDevelopment({ error, viewPath, phase, }: ErrorPageDeve
|
|
|
417
533
|
*/
|
|
418
534
|
declare function ErrorPageProduction(): react_jsx_runtime.JSX.Element;
|
|
419
535
|
|
|
420
|
-
export { ErrorPageDevelopment as E, type HeadData as H,
|
|
536
|
+
export { ErrorPageDevelopment as E, type HeadData as H, type RenderResponse as R, StreamingErrorHandler as S, TemplateParserService as T, RenderModule as a, RenderService as b, RenderInterceptor as c, type RenderConfig as d, type SSRMode as e, ErrorPageProduction as f };
|
|
@@ -40,6 +40,21 @@ interface HeadData {
|
|
|
40
40
|
content: string;
|
|
41
41
|
[key: string]: any;
|
|
42
42
|
}>;
|
|
43
|
+
/** Script tags for analytics, tracking, etc. */
|
|
44
|
+
scripts?: Array<{
|
|
45
|
+
src?: string;
|
|
46
|
+
async?: boolean;
|
|
47
|
+
defer?: boolean;
|
|
48
|
+
type?: string;
|
|
49
|
+
innerHTML?: string;
|
|
50
|
+
[key: string]: any;
|
|
51
|
+
}>;
|
|
52
|
+
/** JSON-LD structured data for search engines */
|
|
53
|
+
jsonLd?: Array<Record<string, any>>;
|
|
54
|
+
/** Attributes to add to <html> tag (e.g., lang, dir) */
|
|
55
|
+
htmlAttributes?: Record<string, string>;
|
|
56
|
+
/** Attributes to add to <body> tag (e.g., class, data-theme) */
|
|
57
|
+
bodyAttributes?: Record<string, string>;
|
|
43
58
|
}
|
|
44
59
|
/**
|
|
45
60
|
* Response structure for SSR rendering
|
|
@@ -57,12 +72,16 @@ interface HeadData {
|
|
|
57
72
|
* // Treated as: { props: { message: 'Hello' } }
|
|
58
73
|
* }
|
|
59
74
|
*
|
|
60
|
-
* // Advanced case - with head data
|
|
75
|
+
* // Advanced case - with head data and layout props
|
|
61
76
|
* @Render('views/user')
|
|
62
77
|
* getUser(@Param('id') id: string) {
|
|
63
78
|
* const user = await this.userService.findOne(id);
|
|
64
79
|
* return {
|
|
65
80
|
* props: { user },
|
|
81
|
+
* layoutProps: {
|
|
82
|
+
* title: user.name,
|
|
83
|
+
* subtitle: 'User Profile'
|
|
84
|
+
* },
|
|
66
85
|
* head: {
|
|
67
86
|
* title: `${user.name} - Profile`,
|
|
68
87
|
* description: user.bio,
|
|
@@ -77,6 +96,17 @@ interface RenderResponse<T = any> {
|
|
|
77
96
|
props: T;
|
|
78
97
|
/** HTML head data (title, meta tags, links) */
|
|
79
98
|
head?: HeadData;
|
|
99
|
+
/**
|
|
100
|
+
* Props passed to layout components (dynamic, per-request)
|
|
101
|
+
*
|
|
102
|
+
* These props are merged with static layout props from decorators:
|
|
103
|
+
* - Static props from @Layout decorator (controller level)
|
|
104
|
+
* - Static props from @Render decorator (method level)
|
|
105
|
+
* - Dynamic props from this field (highest priority)
|
|
106
|
+
*
|
|
107
|
+
* All layout components in the hierarchy receive the merged props.
|
|
108
|
+
*/
|
|
109
|
+
layoutProps?: Record<string, any>;
|
|
80
110
|
}
|
|
81
111
|
|
|
82
112
|
/**
|
|
@@ -154,6 +184,32 @@ interface RenderConfig {
|
|
|
154
184
|
* ```
|
|
155
185
|
*/
|
|
156
186
|
vite?: ViteConfig;
|
|
187
|
+
/**
|
|
188
|
+
* Custom HTML template for SSR
|
|
189
|
+
* Provide either a file path or template string
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```typescript
|
|
193
|
+
* // File path (absolute or relative to cwd)
|
|
194
|
+
* RenderModule.register({
|
|
195
|
+
* template: './src/views/custom-template.html'
|
|
196
|
+
* })
|
|
197
|
+
*
|
|
198
|
+
* // Template string
|
|
199
|
+
* RenderModule.register({
|
|
200
|
+
* template: `<!DOCTYPE html>
|
|
201
|
+
* <html>
|
|
202
|
+
* <head><!--styles--></head>
|
|
203
|
+
* <body>
|
|
204
|
+
* <div id="root"><!--app-html--></div>
|
|
205
|
+
* <!--initial-state-->
|
|
206
|
+
* <!--client-scripts-->
|
|
207
|
+
* </body>
|
|
208
|
+
* </html>`
|
|
209
|
+
* })
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
template?: string;
|
|
157
213
|
/**
|
|
158
214
|
* Custom error page component for development environment
|
|
159
215
|
* Receives error details and renders custom error UI
|
|
@@ -186,6 +242,41 @@ interface RenderConfig {
|
|
|
186
242
|
* ```
|
|
187
243
|
*/
|
|
188
244
|
defaultHead?: HeadData;
|
|
245
|
+
/**
|
|
246
|
+
* HTTP headers to pass to client
|
|
247
|
+
* By default, no headers are passed for security
|
|
248
|
+
* Use this to opt-in to specific headers that are safe to expose
|
|
249
|
+
*
|
|
250
|
+
* Common safe headers: user-agent, accept-language, referer
|
|
251
|
+
* Security warning: Never include sensitive headers like authorization, cookie, etc.
|
|
252
|
+
*
|
|
253
|
+
* @default []
|
|
254
|
+
*
|
|
255
|
+
* @example
|
|
256
|
+
* ```typescript
|
|
257
|
+
* RenderModule.register({
|
|
258
|
+
* allowedHeaders: ['user-agent', 'accept-language', 'x-tenant-id', 'x-api-version']
|
|
259
|
+
* })
|
|
260
|
+
* ```
|
|
261
|
+
*/
|
|
262
|
+
allowedHeaders?: string[];
|
|
263
|
+
/**
|
|
264
|
+
* Cookie names to pass to client
|
|
265
|
+
* By default, no cookies are passed to client for security
|
|
266
|
+
* Use this to opt-in to specific cookies that are safe to expose
|
|
267
|
+
*
|
|
268
|
+
* Security warning: Never include sensitive cookies like session tokens, auth cookies, etc.
|
|
269
|
+
*
|
|
270
|
+
* @default []
|
|
271
|
+
*
|
|
272
|
+
* @example
|
|
273
|
+
* ```typescript
|
|
274
|
+
* RenderModule.register({
|
|
275
|
+
* allowedCookies: ['theme', 'locale', 'consent']
|
|
276
|
+
* })
|
|
277
|
+
* ```
|
|
278
|
+
*/
|
|
279
|
+
allowedCookies?: string[];
|
|
189
280
|
}
|
|
190
281
|
/**
|
|
191
282
|
* Template parts for streaming SSR
|
|
@@ -291,11 +382,14 @@ declare class TemplateParserService {
|
|
|
291
382
|
/**
|
|
292
383
|
* Build inline script that provides initial state to the client
|
|
293
384
|
*
|
|
294
|
-
* Safely serializes data using
|
|
295
|
-
*
|
|
296
|
-
*
|
|
385
|
+
* Safely serializes data using devalue to avoid XSS vulnerabilities.
|
|
386
|
+
* Devalue is designed specifically for SSR, handling complex types safely
|
|
387
|
+
* while being faster and more secure than alternatives.
|
|
297
388
|
*/
|
|
298
|
-
buildInlineScripts(data: any, context: any, componentName: string
|
|
389
|
+
buildInlineScripts(data: any, context: any, componentName: string, layouts?: Array<{
|
|
390
|
+
layout: any;
|
|
391
|
+
props?: any;
|
|
392
|
+
}>): string;
|
|
299
393
|
/**
|
|
300
394
|
* Get client script tag for hydration
|
|
301
395
|
*
|
|
@@ -368,8 +462,18 @@ declare class RenderService {
|
|
|
368
462
|
private isDevelopment;
|
|
369
463
|
private ssrMode;
|
|
370
464
|
private readonly entryServerPath;
|
|
371
|
-
|
|
465
|
+
private rootLayout;
|
|
466
|
+
private rootLayoutChecked;
|
|
467
|
+
constructor(templateParser: TemplateParserService, streamingErrorHandler: StreamingErrorHandler, ssrMode?: SSRMode, defaultHead?: HeadData | undefined, customTemplate?: string);
|
|
372
468
|
setViteServer(vite: ViteDevServer): void;
|
|
469
|
+
/**
|
|
470
|
+
* Get the root layout component if it exists
|
|
471
|
+
* Auto-discovers layout files at conventional paths:
|
|
472
|
+
* - src/views/layout.tsx
|
|
473
|
+
* - src/views/layout/index.tsx
|
|
474
|
+
* - src/views/_layout.tsx
|
|
475
|
+
*/
|
|
476
|
+
getRootLayout(): Promise<any | null>;
|
|
373
477
|
/**
|
|
374
478
|
* Main render method that routes to string or stream mode
|
|
375
479
|
*/
|
|
@@ -392,7 +496,19 @@ declare class RenderService {
|
|
|
392
496
|
declare class RenderInterceptor implements NestInterceptor {
|
|
393
497
|
private reflector;
|
|
394
498
|
private renderService;
|
|
395
|
-
|
|
499
|
+
private allowedHeaders?;
|
|
500
|
+
private allowedCookies?;
|
|
501
|
+
constructor(reflector: Reflector, renderService: RenderService, allowedHeaders?: string[] | undefined, allowedCookies?: string[] | undefined);
|
|
502
|
+
/**
|
|
503
|
+
* Resolve the layout hierarchy for a given route
|
|
504
|
+
* Hierarchy: Root Layout → Controller Layout → Method Layout → Page
|
|
505
|
+
*
|
|
506
|
+
* Props are merged in priority order:
|
|
507
|
+
* 1. Static props from @Layout decorator (base)
|
|
508
|
+
* 2. Static props from @Render decorator (override)
|
|
509
|
+
* 3. Dynamic props from controller return (final override)
|
|
510
|
+
*/
|
|
511
|
+
private resolveLayoutChain;
|
|
396
512
|
intercept(context: ExecutionContext, next: CallHandler): Observable<any>;
|
|
397
513
|
}
|
|
398
514
|
|
|
@@ -417,4 +533,4 @@ declare function ErrorPageDevelopment({ error, viewPath, phase, }: ErrorPageDeve
|
|
|
417
533
|
*/
|
|
418
534
|
declare function ErrorPageProduction(): react_jsx_runtime.JSX.Element;
|
|
419
535
|
|
|
420
|
-
export { ErrorPageDevelopment as E, type HeadData as H,
|
|
536
|
+
export { ErrorPageDevelopment as E, type HeadData as H, type RenderResponse as R, StreamingErrorHandler as S, TemplateParserService as T, RenderModule as a, RenderService as b, RenderInterceptor as c, type RenderConfig as d, type SSRMode as e, ErrorPageProduction as f };
|