@nestjs-ssr/react 0.3.5 → 0.3.7

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/dist/client.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- export { a as PageContextProvider, P as PageProps, c as createSSRHooks, j as updatePageContext, i as useCookie, h as useCookies, g as useHeader, f as useHeaders, u as usePageContext, b as useParams, d as useQuery, e as useRequest } from './use-page-context-CVC9DHcL.mjs';
1
+ export { a as PageContextProvider, P as PageProps, c as createSSRHooks, j as updatePageContext, u as useCookie, b as useCookies, d as useHeader, e as useHeaders, f as usePageContext, g as useParams, h as useQuery, i as useRequest } from './use-page-context-CmxWHIK3.mjs';
2
2
  import * as react_jsx_runtime from 'react/jsx-runtime';
3
3
  import React from 'react';
4
4
  export { R as RenderContext } from './render-response.interface-ClWJXKL4.mjs';
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { a as PageContextProvider, P as PageProps, c as createSSRHooks, j as updatePageContext, i as useCookie, h as useCookies, g as useHeader, f as useHeaders, u as usePageContext, b as useParams, d as useQuery, e as useRequest } from './use-page-context-DChgHhL9.js';
1
+ export { a as PageContextProvider, P as PageProps, c as createSSRHooks, j as updatePageContext, u as useCookie, b as useCookies, d as useHeader, e as useHeaders, f as usePageContext, g as useParams, h as useQuery, i as useRequest } from './use-page-context-CUV31oda.js';
2
2
  import * as react_jsx_runtime from 'react/jsx-runtime';
3
3
  import React from 'react';
4
4
  export { R as RenderContext } from './render-response.interface-ClWJXKL4.js';
@@ -1,12 +1,79 @@
1
1
  import { DynamicModule, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
2
2
  import { ComponentType } from 'react';
3
- import { Request, Response } from 'express';
4
3
  import { H as HeadData, R as RenderContext } from './render-response.interface-ClWJXKL4.js';
4
+ import { ServerResponse } from 'http';
5
5
  import { ViteDevServer } from 'vite';
6
6
  import { Reflector } from '@nestjs/core';
7
7
  import { Observable } from 'rxjs';
8
8
  import * as react_jsx_runtime from 'react/jsx-runtime';
9
9
 
10
+ /**
11
+ * Common HTTP request interface that works with both Express and Fastify.
12
+ * This represents the minimal interface needed for SSR context building.
13
+ */
14
+ interface SSRRequest {
15
+ /** Full request URL including query string */
16
+ url: string;
17
+ /** HTTP method (GET, POST, etc.) */
18
+ method: string;
19
+ /** Request headers */
20
+ headers: Record<string, string | string[] | undefined>;
21
+ /** URL path (Express: path, Fastify: routeOptions.url or url without query) */
22
+ path?: string;
23
+ /** Parsed query parameters */
24
+ query?: Record<string, string | string[] | undefined>;
25
+ /** Route parameters */
26
+ params?: Record<string, string>;
27
+ /** Parsed cookies (requires cookie-parser middleware) */
28
+ cookies?: Record<string, string>;
29
+ /**
30
+ * User object populated by authentication middleware (e.g., Passport).
31
+ * Type is `unknown` since the shape depends on your auth strategy.
32
+ */
33
+ user?: unknown;
34
+ /** Allow any additional properties for framework-specific extensions */
35
+ [key: string]: unknown;
36
+ }
37
+ /**
38
+ * Minimal interface for the raw Node.js response.
39
+ * This is a subset of ServerResponse that we actually use for streaming SSR.
40
+ */
41
+ interface RawServerResponse {
42
+ statusCode: number;
43
+ headersSent: boolean;
44
+ writableEnded: boolean;
45
+ setHeader(name: string, value: string | number | readonly string[]): void;
46
+ write(chunk: string | Buffer): boolean;
47
+ end(data?: string | Buffer): void;
48
+ on?(event: string, listener: (...args: any[]) => void): this;
49
+ }
50
+ /**
51
+ * Common HTTP response interface that works with both Express and Fastify.
52
+ * For streaming SSR, we access the raw Node.js ServerResponse.
53
+ */
54
+ interface SSRResponse {
55
+ /** HTTP status code (optional - Fastify has it on raw) */
56
+ statusCode?: number;
57
+ /** Whether headers have been sent (Express) */
58
+ headersSent?: boolean;
59
+ /** Whether headers have been sent (Fastify uses 'sent') */
60
+ sent?: boolean;
61
+ /** Whether the response stream has ended */
62
+ writableEnded?: boolean;
63
+ /** Set a response header */
64
+ setHeader?(name: string, value: string | number | readonly string[]): void;
65
+ /** Write data to the response */
66
+ write?(chunk: string | Buffer): boolean;
67
+ /** End the response */
68
+ end?(data?: string | Buffer): void;
69
+ /** Event listener for 'close' event */
70
+ on?(event: string, listener: (...args: any[]) => void): this;
71
+ /** Raw Node.js response (Fastify) - uses minimal interface for easier testing */
72
+ raw?: RawServerResponse | ServerResponse;
73
+ /** Allow additional properties */
74
+ [key: string]: unknown;
75
+ }
76
+
10
77
  /**
11
78
  * Custom context properties that can be added via context factory.
12
79
  * Allows any properties to be merged into RenderContext.
@@ -16,7 +83,7 @@ type CustomContextProperties = Record<string, unknown>;
16
83
  * Context factory function signature
17
84
  * Called for each request to build custom context properties
18
85
  *
19
- * @param params - Object containing the Express request
86
+ * @param params - Object containing the HTTP request (Express or Fastify)
20
87
  * @returns Custom context properties to merge into RenderContext (sync or async)
21
88
  *
22
89
  * @example
@@ -37,8 +104,9 @@ type CustomContextProperties = Record<string, unknown>;
37
104
  * })
38
105
  * ```
39
106
  */
40
- type ContextFactory = (params: {
41
- req: Request;
107
+ type ContextFactory<TRequest extends SSRRequest = SSRRequest> = (params: {
108
+ /** HTTP request object (Express Request or Fastify FastifyRequest) */
109
+ req: TRequest;
42
110
  }) => CustomContextProperties | Promise<CustomContextProperties>;
43
111
  /**
44
112
  * SSR rendering mode configuration
@@ -486,7 +554,7 @@ declare class StreamingErrorHandler {
486
554
  * Handle error that occurred before shell was ready
487
555
  * Can still set HTTP status code and send error page
488
556
  */
489
- handleShellError(error: Error, res: Response, viewPath: string, isDevelopment: boolean): void;
557
+ handleShellError(error: Error, res: SSRResponse, viewPath: string, isDevelopment: boolean): void;
490
558
  /**
491
559
  * Handle error that occurred during streaming
492
560
  * Headers already sent, can only log the error
@@ -552,11 +620,11 @@ declare class StreamRenderer {
552
620
  *
553
621
  * @param viewComponent - The React component to render
554
622
  * @param data - Data to pass to the component
555
- * @param res - Express response object (required for streaming)
623
+ * @param res - HTTP response object (Express or Fastify)
556
624
  * @param context - Render context with Vite and manifest info
557
625
  * @param head - Head data for SEO tags
558
626
  */
559
- render(viewComponent: any, data: any, res: Response, context: StreamRenderContext, head?: HeadData): Promise<void>;
627
+ render(viewComponent: any, data: any, res: SSRResponse, context: StreamRenderContext, head?: HeadData): Promise<void>;
560
628
  }
561
629
 
562
630
  /**
@@ -621,7 +689,7 @@ declare class RenderService {
621
689
  * - Better TTFB, progressive rendering
622
690
  * - Requires response object
623
691
  */
624
- render(viewComponent: any, data?: any, res?: Response, head?: HeadData): Promise<string | void>;
692
+ render(viewComponent: any, data?: any, res?: SSRResponse, head?: HeadData): Promise<string | void>;
625
693
  /**
626
694
  * Render a segment for client-side navigation.
627
695
  * Always uses string mode (streaming not supported for segments).
@@ -691,4 +759,4 @@ declare function ErrorPageDevelopment({ error, viewPath, phase, }: ErrorPageDeve
691
759
  */
692
760
  declare function ErrorPageProduction(): react_jsx_runtime.JSX.Element;
693
761
 
694
- export { type ContextFactory as C, ErrorPageDevelopment as E, RenderModule as R, StreamingErrorHandler as S, TemplateParserService as T, RenderService as a, RenderInterceptor as b, type RenderConfig as c, type SSRMode as d, ErrorPageProduction as e };
762
+ export { type ContextFactory as C, ErrorPageDevelopment as E, type RenderConfig as R, type SSRMode as S, TemplateParserService as T, ErrorPageProduction as a, RenderInterceptor as b, RenderModule as c, RenderService as d, StreamingErrorHandler as e };
@@ -1,12 +1,79 @@
1
1
  import { DynamicModule, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
2
2
  import { ComponentType } from 'react';
3
- import { Request, Response } from 'express';
4
3
  import { H as HeadData, R as RenderContext } from './render-response.interface-ClWJXKL4.mjs';
4
+ import { ServerResponse } from 'http';
5
5
  import { ViteDevServer } from 'vite';
6
6
  import { Reflector } from '@nestjs/core';
7
7
  import { Observable } from 'rxjs';
8
8
  import * as react_jsx_runtime from 'react/jsx-runtime';
9
9
 
10
+ /**
11
+ * Common HTTP request interface that works with both Express and Fastify.
12
+ * This represents the minimal interface needed for SSR context building.
13
+ */
14
+ interface SSRRequest {
15
+ /** Full request URL including query string */
16
+ url: string;
17
+ /** HTTP method (GET, POST, etc.) */
18
+ method: string;
19
+ /** Request headers */
20
+ headers: Record<string, string | string[] | undefined>;
21
+ /** URL path (Express: path, Fastify: routeOptions.url or url without query) */
22
+ path?: string;
23
+ /** Parsed query parameters */
24
+ query?: Record<string, string | string[] | undefined>;
25
+ /** Route parameters */
26
+ params?: Record<string, string>;
27
+ /** Parsed cookies (requires cookie-parser middleware) */
28
+ cookies?: Record<string, string>;
29
+ /**
30
+ * User object populated by authentication middleware (e.g., Passport).
31
+ * Type is `unknown` since the shape depends on your auth strategy.
32
+ */
33
+ user?: unknown;
34
+ /** Allow any additional properties for framework-specific extensions */
35
+ [key: string]: unknown;
36
+ }
37
+ /**
38
+ * Minimal interface for the raw Node.js response.
39
+ * This is a subset of ServerResponse that we actually use for streaming SSR.
40
+ */
41
+ interface RawServerResponse {
42
+ statusCode: number;
43
+ headersSent: boolean;
44
+ writableEnded: boolean;
45
+ setHeader(name: string, value: string | number | readonly string[]): void;
46
+ write(chunk: string | Buffer): boolean;
47
+ end(data?: string | Buffer): void;
48
+ on?(event: string, listener: (...args: any[]) => void): this;
49
+ }
50
+ /**
51
+ * Common HTTP response interface that works with both Express and Fastify.
52
+ * For streaming SSR, we access the raw Node.js ServerResponse.
53
+ */
54
+ interface SSRResponse {
55
+ /** HTTP status code (optional - Fastify has it on raw) */
56
+ statusCode?: number;
57
+ /** Whether headers have been sent (Express) */
58
+ headersSent?: boolean;
59
+ /** Whether headers have been sent (Fastify uses 'sent') */
60
+ sent?: boolean;
61
+ /** Whether the response stream has ended */
62
+ writableEnded?: boolean;
63
+ /** Set a response header */
64
+ setHeader?(name: string, value: string | number | readonly string[]): void;
65
+ /** Write data to the response */
66
+ write?(chunk: string | Buffer): boolean;
67
+ /** End the response */
68
+ end?(data?: string | Buffer): void;
69
+ /** Event listener for 'close' event */
70
+ on?(event: string, listener: (...args: any[]) => void): this;
71
+ /** Raw Node.js response (Fastify) - uses minimal interface for easier testing */
72
+ raw?: RawServerResponse | ServerResponse;
73
+ /** Allow additional properties */
74
+ [key: string]: unknown;
75
+ }
76
+
10
77
  /**
11
78
  * Custom context properties that can be added via context factory.
12
79
  * Allows any properties to be merged into RenderContext.
@@ -16,7 +83,7 @@ type CustomContextProperties = Record<string, unknown>;
16
83
  * Context factory function signature
17
84
  * Called for each request to build custom context properties
18
85
  *
19
- * @param params - Object containing the Express request
86
+ * @param params - Object containing the HTTP request (Express or Fastify)
20
87
  * @returns Custom context properties to merge into RenderContext (sync or async)
21
88
  *
22
89
  * @example
@@ -37,8 +104,9 @@ type CustomContextProperties = Record<string, unknown>;
37
104
  * })
38
105
  * ```
39
106
  */
40
- type ContextFactory = (params: {
41
- req: Request;
107
+ type ContextFactory<TRequest extends SSRRequest = SSRRequest> = (params: {
108
+ /** HTTP request object (Express Request or Fastify FastifyRequest) */
109
+ req: TRequest;
42
110
  }) => CustomContextProperties | Promise<CustomContextProperties>;
43
111
  /**
44
112
  * SSR rendering mode configuration
@@ -486,7 +554,7 @@ declare class StreamingErrorHandler {
486
554
  * Handle error that occurred before shell was ready
487
555
  * Can still set HTTP status code and send error page
488
556
  */
489
- handleShellError(error: Error, res: Response, viewPath: string, isDevelopment: boolean): void;
557
+ handleShellError(error: Error, res: SSRResponse, viewPath: string, isDevelopment: boolean): void;
490
558
  /**
491
559
  * Handle error that occurred during streaming
492
560
  * Headers already sent, can only log the error
@@ -552,11 +620,11 @@ declare class StreamRenderer {
552
620
  *
553
621
  * @param viewComponent - The React component to render
554
622
  * @param data - Data to pass to the component
555
- * @param res - Express response object (required for streaming)
623
+ * @param res - HTTP response object (Express or Fastify)
556
624
  * @param context - Render context with Vite and manifest info
557
625
  * @param head - Head data for SEO tags
558
626
  */
559
- render(viewComponent: any, data: any, res: Response, context: StreamRenderContext, head?: HeadData): Promise<void>;
627
+ render(viewComponent: any, data: any, res: SSRResponse, context: StreamRenderContext, head?: HeadData): Promise<void>;
560
628
  }
561
629
 
562
630
  /**
@@ -621,7 +689,7 @@ declare class RenderService {
621
689
  * - Better TTFB, progressive rendering
622
690
  * - Requires response object
623
691
  */
624
- render(viewComponent: any, data?: any, res?: Response, head?: HeadData): Promise<string | void>;
692
+ render(viewComponent: any, data?: any, res?: SSRResponse, head?: HeadData): Promise<string | void>;
625
693
  /**
626
694
  * Render a segment for client-side navigation.
627
695
  * Always uses string mode (streaming not supported for segments).
@@ -691,4 +759,4 @@ declare function ErrorPageDevelopment({ error, viewPath, phase, }: ErrorPageDeve
691
759
  */
692
760
  declare function ErrorPageProduction(): react_jsx_runtime.JSX.Element;
693
761
 
694
- export { type ContextFactory as C, ErrorPageDevelopment as E, RenderModule as R, StreamingErrorHandler as S, TemplateParserService as T, RenderService as a, RenderInterceptor as b, type RenderConfig as c, type SSRMode as d, ErrorPageProduction as e };
762
+ export { type ContextFactory as C, ErrorPageDevelopment as E, type RenderConfig as R, type SSRMode as S, TemplateParserService as T, ErrorPageProduction as a, RenderInterceptor as b, RenderModule as c, RenderService as d, StreamingErrorHandler as e };
package/dist/index.d.mts CHANGED
@@ -1,10 +1,10 @@
1
- export { C as ContextFactory, E as ErrorPageDevelopment, e as ErrorPageProduction, c as RenderConfig, b as RenderInterceptor, R as RenderModule, a as RenderService, d as SSRMode, S as StreamingErrorHandler, T as TemplateParserService } from './index-CiYcz-1T.mjs';
1
+ export { C as ContextFactory, E as ErrorPageDevelopment, a as ErrorPageProduction, R as RenderConfig, b as RenderInterceptor, c as RenderModule, d as RenderService, S as SSRMode, e as StreamingErrorHandler, T as TemplateParserService } from './index-DcpOFSp4.mjs';
2
2
  import React, { ComponentType, ReactNode } from 'react';
3
- import { P as PageProps } from './use-page-context-CVC9DHcL.mjs';
4
- export { a as PageContextProvider, c as createSSRHooks, i as useCookie, h as useCookies, g as useHeader, f as useHeaders, u as usePageContext, b as useParams, d as useQuery, e as useRequest } from './use-page-context-CVC9DHcL.mjs';
3
+ import { P as PageProps } from './use-page-context-CmxWHIK3.mjs';
4
+ export { a as PageContextProvider, c as createSSRHooks, u as useCookie, b as useCookies, d as useHeader, e as useHeaders, f as usePageContext, g as useParams, h as useQuery, i as useRequest } from './use-page-context-CmxWHIK3.mjs';
5
5
  import { R as RenderContext, H as HeadData, a as RenderResponse } from './render-response.interface-ClWJXKL4.mjs';
6
6
  import '@nestjs/common';
7
- import 'express';
7
+ import 'http';
8
8
  import 'vite';
9
9
  import '@nestjs/core';
10
10
  import 'rxjs';
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- export { C as ContextFactory, E as ErrorPageDevelopment, e as ErrorPageProduction, c as RenderConfig, b as RenderInterceptor, R as RenderModule, a as RenderService, d as SSRMode, S as StreamingErrorHandler, T as TemplateParserService } from './index-Dq2qZSge.js';
1
+ export { C as ContextFactory, E as ErrorPageDevelopment, a as ErrorPageProduction, R as RenderConfig, b as RenderInterceptor, c as RenderModule, d as RenderService, S as SSRMode, e as StreamingErrorHandler, T as TemplateParserService } from './index-CGfEDKI4.js';
2
2
  import React, { ComponentType, ReactNode } from 'react';
3
- import { P as PageProps } from './use-page-context-DChgHhL9.js';
4
- export { a as PageContextProvider, c as createSSRHooks, i as useCookie, h as useCookies, g as useHeader, f as useHeaders, u as usePageContext, b as useParams, d as useQuery, e as useRequest } from './use-page-context-DChgHhL9.js';
3
+ import { P as PageProps } from './use-page-context-CUV31oda.js';
4
+ export { a as PageContextProvider, c as createSSRHooks, u as useCookie, b as useCookies, d as useHeader, e as useHeaders, f as usePageContext, g as useParams, h as useQuery, i as useRequest } from './use-page-context-CUV31oda.js';
5
5
  import { R as RenderContext, H as HeadData, a as RenderResponse } from './render-response.interface-ClWJXKL4.js';
6
6
  import '@nestjs/common';
7
- import 'express';
7
+ import 'http';
8
8
  import 'vite';
9
9
  import '@nestjs/core';
10
10
  import 'rxjs';
package/dist/index.js CHANGED
@@ -462,6 +462,39 @@ function ErrorPageProduction() {
462
462
  }
463
463
  __name(ErrorPageProduction, "ErrorPageProduction");
464
464
 
465
+ // src/render/adapters/http-adapter-utils.ts
466
+ function detectAdapterType(httpAdapterHost) {
467
+ const adapter = httpAdapterHost?.httpAdapter;
468
+ if (!adapter) return "unknown";
469
+ const instance = adapter.getInstance();
470
+ if (instance && typeof instance.register === "function") {
471
+ return "fastify";
472
+ }
473
+ if (instance && typeof instance.use === "function" && typeof instance.get === "function") {
474
+ return "express";
475
+ }
476
+ return "unknown";
477
+ }
478
+ __name(detectAdapterType, "detectAdapterType");
479
+ function isFastifyLikeResponse(res) {
480
+ return res != null && typeof res === "object" && "raw" in res && res.raw != null && typeof res.raw.write === "function";
481
+ }
482
+ __name(isFastifyLikeResponse, "isFastifyLikeResponse");
483
+ function getRawResponse(res) {
484
+ if (isFastifyLikeResponse(res)) {
485
+ return res.raw;
486
+ }
487
+ return res;
488
+ }
489
+ __name(getRawResponse, "getRawResponse");
490
+ function isHeadersSent(res) {
491
+ if (typeof res.sent === "boolean") {
492
+ return res.sent;
493
+ }
494
+ return res.headersSent === true;
495
+ }
496
+ __name(isHeadersSent, "isHeadersSent");
497
+
465
498
  // src/render/streaming-error-handler.ts
466
499
  function _ts_decorate3(decorators, target, key, desc) {
467
500
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -497,21 +530,19 @@ exports.StreamingErrorHandler = class _StreamingErrorHandler {
497
530
  */
498
531
  handleShellError(error, res, viewPath, isDevelopment) {
499
532
  this.logger.error(`Shell error rendering ${viewPath}: ${error.message}`, error.stack);
500
- if (res.headersSent) {
533
+ const rawRes = getRawResponse(res);
534
+ if (isHeadersSent(res)) {
501
535
  this.logger.error(`Cannot send error page for ${viewPath} - headers already sent (streaming started)`);
502
- if (!res.writableEnded) {
503
- res.write(this.renderInlineErrorOverlay(error, viewPath, isDevelopment));
504
- res.end();
536
+ if (!rawRes.writableEnded) {
537
+ rawRes.write(this.renderInlineErrorOverlay(error, viewPath, isDevelopment));
538
+ rawRes.end();
505
539
  }
506
540
  return;
507
541
  }
508
- res.statusCode = 500;
509
- res.setHeader("Content-Type", "text/html; charset=utf-8");
510
- if (isDevelopment) {
511
- res.send(this.renderDevelopmentErrorPage(error, viewPath, "shell"));
512
- } else {
513
- res.send(this.renderProductionErrorPage());
514
- }
542
+ rawRes.statusCode = 500;
543
+ rawRes.setHeader("Content-Type", "text/html; charset=utf-8");
544
+ const html = isDevelopment ? this.renderDevelopmentErrorPage(error, viewPath, "shell") : this.renderProductionErrorPage();
545
+ rawRes.end(html);
515
546
  }
516
547
  /**
517
548
  * Handle error that occurred during streaming
@@ -667,7 +698,7 @@ var StreamRenderer = class _StreamRenderer {
667
698
  *
668
699
  * @param viewComponent - The React component to render
669
700
  * @param data - Data to pass to the component
670
- * @param res - Express response object (required for streaming)
701
+ * @param res - HTTP response object (Express or Fastify)
671
702
  * @param context - Render context with Vite and manifest info
672
703
  * @param head - Head data for SEO tags
673
704
  */
@@ -709,20 +740,21 @@ var StreamRenderer = class _StreamRenderer {
709
740
  const { PassThrough } = await import('stream');
710
741
  const reactStream = new PassThrough();
711
742
  let allReadyFired = false;
743
+ const rawRes = getRawResponse(res);
712
744
  const { pipe, abort } = renderModule.renderComponentStream(viewComponent, data, {
713
745
  onShellReady: /* @__PURE__ */ __name(() => {
714
746
  shellReadyTime = Date.now();
715
- if (!res.headersSent) {
716
- res.statusCode = didError ? 500 : 200;
717
- res.setHeader("Content-Type", "text/html; charset=utf-8");
747
+ if (!rawRes.headersSent) {
748
+ rawRes.statusCode = didError ? 500 : 200;
749
+ rawRes.setHeader("Content-Type", "text/html; charset=utf-8");
718
750
  }
719
751
  let htmlStart = templateParts.htmlStart;
720
752
  htmlStart = htmlStart.replace("<!--styles-->", stylesheetTags);
721
753
  htmlStart = htmlStart.replace("<!--head-meta-->", headTags);
722
- res.write(htmlStart);
723
- res.write(templateParts.rootStart);
754
+ rawRes.write(htmlStart);
755
+ rawRes.write(templateParts.rootStart);
724
756
  pipe(reactStream);
725
- reactStream.pipe(res, {
757
+ reactStream.pipe(rawRes, {
726
758
  end: false
727
759
  });
728
760
  if (context.isDevelopment) {
@@ -747,11 +779,11 @@ var StreamRenderer = class _StreamRenderer {
747
779
  if (shellErrorOccurred) {
748
780
  return;
749
781
  }
750
- res.write(inlineScripts);
751
- res.write(clientScript);
752
- res.write(templateParts.rootEnd);
753
- res.write(templateParts.htmlEnd);
754
- res.end();
782
+ rawRes.write(inlineScripts);
783
+ rawRes.write(clientScript);
784
+ rawRes.write(templateParts.rootEnd);
785
+ rawRes.write(templateParts.htmlEnd);
786
+ rawRes.end();
755
787
  if (context.isDevelopment) {
756
788
  const totalTime = Date.now() - startTime;
757
789
  const streamTime = Date.now() - shellReadyTime;
@@ -763,7 +795,7 @@ var StreamRenderer = class _StreamRenderer {
763
795
  reactStream.on("error", (error) => {
764
796
  reject(error);
765
797
  });
766
- res.on("close", () => {
798
+ rawRes.on("close", () => {
767
799
  abort();
768
800
  resolve();
769
801
  });
@@ -1221,9 +1253,10 @@ exports.RenderInterceptor = class RenderInterceptor {
1221
1253
  if (typeof data === "string") {
1222
1254
  return data;
1223
1255
  }
1256
+ const requestPath = request.path ?? request.url?.split("?")[0] ?? "/";
1224
1257
  const renderContext = {
1225
1258
  url: request.url,
1226
- path: request.path,
1259
+ path: requestPath,
1227
1260
  query: request.query,
1228
1261
  params: request.params,
1229
1262
  method: request.method
@@ -1361,7 +1394,7 @@ var ViteInitializerService = class _ViteInitializerService {
1361
1394
  if (isDevelopment) {
1362
1395
  await this.setupDevelopmentMode();
1363
1396
  } else {
1364
- this.setupProductionMode();
1397
+ await this.setupProductionMode();
1365
1398
  }
1366
1399
  }
1367
1400
  async setupDevelopmentMode() {
@@ -1403,18 +1436,38 @@ var ViteInitializerService = class _ViteInitializerService {
1403
1436
  this.logger.warn(`Failed to setup Vite proxy: ${error.message}. Make sure http-proxy-middleware is installed.`);
1404
1437
  }
1405
1438
  }
1406
- setupProductionMode() {
1439
+ async setupProductionMode() {
1407
1440
  try {
1408
1441
  const httpAdapter = this.httpAdapterHost.httpAdapter;
1409
- if (httpAdapter) {
1410
- const app = httpAdapter.getInstance();
1411
- const { join: join2 } = __require("path");
1442
+ if (!httpAdapter) return;
1443
+ const app = httpAdapter.getInstance();
1444
+ const { join: join2 } = __require("path");
1445
+ const staticPath = join2(process.cwd(), "dist/client");
1446
+ const adapterType = detectAdapterType(this.httpAdapterHost);
1447
+ if (adapterType === "fastify") {
1448
+ try {
1449
+ const fastifyStatic = await import('@fastify/static').catch(() => null);
1450
+ if (fastifyStatic) {
1451
+ await app.register(fastifyStatic.default, {
1452
+ root: staticPath,
1453
+ prefix: "/",
1454
+ index: false,
1455
+ maxAge: 31536e6
1456
+ });
1457
+ this.logger.log("\u2713 Static assets configured (dist/client) [Fastify]");
1458
+ } else {
1459
+ this.logger.warn("For Fastify static file serving, install @fastify/static: npm install @fastify/static");
1460
+ }
1461
+ } catch {
1462
+ this.logger.warn("For Fastify static file serving, install @fastify/static: npm install @fastify/static");
1463
+ }
1464
+ } else {
1412
1465
  const express = __require("express");
1413
- app.use(express.static(join2(process.cwd(), "dist/client"), {
1466
+ app.use(express.static(staticPath, {
1414
1467
  index: false,
1415
1468
  maxAge: "1y"
1416
1469
  }));
1417
- this.logger.log("\u2713 Static assets configured (dist/client)");
1470
+ this.logger.log("\u2713 Static assets configured (dist/client) [Express]");
1418
1471
  }
1419
1472
  } catch (error) {
1420
1473
  this.logger.warn(`Failed to setup static assets: ${error.message}`);
package/dist/index.mjs CHANGED
@@ -455,6 +455,39 @@ function ErrorPageProduction() {
455
455
  }
456
456
  __name(ErrorPageProduction, "ErrorPageProduction");
457
457
 
458
+ // src/render/adapters/http-adapter-utils.ts
459
+ function detectAdapterType(httpAdapterHost) {
460
+ const adapter = httpAdapterHost?.httpAdapter;
461
+ if (!adapter) return "unknown";
462
+ const instance = adapter.getInstance();
463
+ if (instance && typeof instance.register === "function") {
464
+ return "fastify";
465
+ }
466
+ if (instance && typeof instance.use === "function" && typeof instance.get === "function") {
467
+ return "express";
468
+ }
469
+ return "unknown";
470
+ }
471
+ __name(detectAdapterType, "detectAdapterType");
472
+ function isFastifyLikeResponse(res) {
473
+ return res != null && typeof res === "object" && "raw" in res && res.raw != null && typeof res.raw.write === "function";
474
+ }
475
+ __name(isFastifyLikeResponse, "isFastifyLikeResponse");
476
+ function getRawResponse(res) {
477
+ if (isFastifyLikeResponse(res)) {
478
+ return res.raw;
479
+ }
480
+ return res;
481
+ }
482
+ __name(getRawResponse, "getRawResponse");
483
+ function isHeadersSent(res) {
484
+ if (typeof res.sent === "boolean") {
485
+ return res.sent;
486
+ }
487
+ return res.headersSent === true;
488
+ }
489
+ __name(isHeadersSent, "isHeadersSent");
490
+
458
491
  // src/render/streaming-error-handler.ts
459
492
  function _ts_decorate3(decorators, target, key, desc) {
460
493
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -490,21 +523,19 @@ var StreamingErrorHandler = class _StreamingErrorHandler {
490
523
  */
491
524
  handleShellError(error, res, viewPath, isDevelopment) {
492
525
  this.logger.error(`Shell error rendering ${viewPath}: ${error.message}`, error.stack);
493
- if (res.headersSent) {
526
+ const rawRes = getRawResponse(res);
527
+ if (isHeadersSent(res)) {
494
528
  this.logger.error(`Cannot send error page for ${viewPath} - headers already sent (streaming started)`);
495
- if (!res.writableEnded) {
496
- res.write(this.renderInlineErrorOverlay(error, viewPath, isDevelopment));
497
- res.end();
529
+ if (!rawRes.writableEnded) {
530
+ rawRes.write(this.renderInlineErrorOverlay(error, viewPath, isDevelopment));
531
+ rawRes.end();
498
532
  }
499
533
  return;
500
534
  }
501
- res.statusCode = 500;
502
- res.setHeader("Content-Type", "text/html; charset=utf-8");
503
- if (isDevelopment) {
504
- res.send(this.renderDevelopmentErrorPage(error, viewPath, "shell"));
505
- } else {
506
- res.send(this.renderProductionErrorPage());
507
- }
535
+ rawRes.statusCode = 500;
536
+ rawRes.setHeader("Content-Type", "text/html; charset=utf-8");
537
+ const html = isDevelopment ? this.renderDevelopmentErrorPage(error, viewPath, "shell") : this.renderProductionErrorPage();
538
+ rawRes.end(html);
508
539
  }
509
540
  /**
510
541
  * Handle error that occurred during streaming
@@ -660,7 +691,7 @@ var StreamRenderer = class _StreamRenderer {
660
691
  *
661
692
  * @param viewComponent - The React component to render
662
693
  * @param data - Data to pass to the component
663
- * @param res - Express response object (required for streaming)
694
+ * @param res - HTTP response object (Express or Fastify)
664
695
  * @param context - Render context with Vite and manifest info
665
696
  * @param head - Head data for SEO tags
666
697
  */
@@ -702,20 +733,21 @@ var StreamRenderer = class _StreamRenderer {
702
733
  const { PassThrough } = await import('stream');
703
734
  const reactStream = new PassThrough();
704
735
  let allReadyFired = false;
736
+ const rawRes = getRawResponse(res);
705
737
  const { pipe, abort } = renderModule.renderComponentStream(viewComponent, data, {
706
738
  onShellReady: /* @__PURE__ */ __name(() => {
707
739
  shellReadyTime = Date.now();
708
- if (!res.headersSent) {
709
- res.statusCode = didError ? 500 : 200;
710
- res.setHeader("Content-Type", "text/html; charset=utf-8");
740
+ if (!rawRes.headersSent) {
741
+ rawRes.statusCode = didError ? 500 : 200;
742
+ rawRes.setHeader("Content-Type", "text/html; charset=utf-8");
711
743
  }
712
744
  let htmlStart = templateParts.htmlStart;
713
745
  htmlStart = htmlStart.replace("<!--styles-->", stylesheetTags);
714
746
  htmlStart = htmlStart.replace("<!--head-meta-->", headTags);
715
- res.write(htmlStart);
716
- res.write(templateParts.rootStart);
747
+ rawRes.write(htmlStart);
748
+ rawRes.write(templateParts.rootStart);
717
749
  pipe(reactStream);
718
- reactStream.pipe(res, {
750
+ reactStream.pipe(rawRes, {
719
751
  end: false
720
752
  });
721
753
  if (context.isDevelopment) {
@@ -740,11 +772,11 @@ var StreamRenderer = class _StreamRenderer {
740
772
  if (shellErrorOccurred) {
741
773
  return;
742
774
  }
743
- res.write(inlineScripts);
744
- res.write(clientScript);
745
- res.write(templateParts.rootEnd);
746
- res.write(templateParts.htmlEnd);
747
- res.end();
775
+ rawRes.write(inlineScripts);
776
+ rawRes.write(clientScript);
777
+ rawRes.write(templateParts.rootEnd);
778
+ rawRes.write(templateParts.htmlEnd);
779
+ rawRes.end();
748
780
  if (context.isDevelopment) {
749
781
  const totalTime = Date.now() - startTime;
750
782
  const streamTime = Date.now() - shellReadyTime;
@@ -756,7 +788,7 @@ var StreamRenderer = class _StreamRenderer {
756
788
  reactStream.on("error", (error) => {
757
789
  reject(error);
758
790
  });
759
- res.on("close", () => {
791
+ rawRes.on("close", () => {
760
792
  abort();
761
793
  resolve();
762
794
  });
@@ -1214,9 +1246,10 @@ var RenderInterceptor = class {
1214
1246
  if (typeof data === "string") {
1215
1247
  return data;
1216
1248
  }
1249
+ const requestPath = request.path ?? request.url?.split("?")[0] ?? "/";
1217
1250
  const renderContext = {
1218
1251
  url: request.url,
1219
- path: request.path,
1252
+ path: requestPath,
1220
1253
  query: request.query,
1221
1254
  params: request.params,
1222
1255
  method: request.method
@@ -1354,7 +1387,7 @@ var ViteInitializerService = class _ViteInitializerService {
1354
1387
  if (isDevelopment) {
1355
1388
  await this.setupDevelopmentMode();
1356
1389
  } else {
1357
- this.setupProductionMode();
1390
+ await this.setupProductionMode();
1358
1391
  }
1359
1392
  }
1360
1393
  async setupDevelopmentMode() {
@@ -1396,18 +1429,38 @@ var ViteInitializerService = class _ViteInitializerService {
1396
1429
  this.logger.warn(`Failed to setup Vite proxy: ${error.message}. Make sure http-proxy-middleware is installed.`);
1397
1430
  }
1398
1431
  }
1399
- setupProductionMode() {
1432
+ async setupProductionMode() {
1400
1433
  try {
1401
1434
  const httpAdapter = this.httpAdapterHost.httpAdapter;
1402
- if (httpAdapter) {
1403
- const app = httpAdapter.getInstance();
1404
- const { join: join2 } = __require("path");
1435
+ if (!httpAdapter) return;
1436
+ const app = httpAdapter.getInstance();
1437
+ const { join: join2 } = __require("path");
1438
+ const staticPath = join2(process.cwd(), "dist/client");
1439
+ const adapterType = detectAdapterType(this.httpAdapterHost);
1440
+ if (adapterType === "fastify") {
1441
+ try {
1442
+ const fastifyStatic = await import('@fastify/static').catch(() => null);
1443
+ if (fastifyStatic) {
1444
+ await app.register(fastifyStatic.default, {
1445
+ root: staticPath,
1446
+ prefix: "/",
1447
+ index: false,
1448
+ maxAge: 31536e6
1449
+ });
1450
+ this.logger.log("\u2713 Static assets configured (dist/client) [Fastify]");
1451
+ } else {
1452
+ this.logger.warn("For Fastify static file serving, install @fastify/static: npm install @fastify/static");
1453
+ }
1454
+ } catch {
1455
+ this.logger.warn("For Fastify static file serving, install @fastify/static: npm install @fastify/static");
1456
+ }
1457
+ } else {
1405
1458
  const express = __require("express");
1406
- app.use(express.static(join2(process.cwd(), "dist/client"), {
1459
+ app.use(express.static(staticPath, {
1407
1460
  index: false,
1408
1461
  maxAge: "1y"
1409
1462
  }));
1410
- this.logger.log("\u2713 Static assets configured (dist/client)");
1463
+ this.logger.log("\u2713 Static assets configured (dist/client) [Express]");
1411
1464
  }
1412
1465
  } catch (error) {
1413
1466
  this.logger.warn(`Failed to setup static assets: ${error.message}`);
@@ -1,8 +1,8 @@
1
- export { E as ErrorPageDevelopment, e as ErrorPageProduction, b as RenderInterceptor, R as RenderModule, a as RenderService, S as StreamingErrorHandler, T as TemplateParserService } from '../index-CiYcz-1T.mjs';
1
+ export { E as ErrorPageDevelopment, a as ErrorPageProduction, b as RenderInterceptor, c as RenderModule, d as RenderService, e as StreamingErrorHandler, T as TemplateParserService } from '../index-DcpOFSp4.mjs';
2
2
  import '@nestjs/common';
3
3
  import 'react';
4
- import 'express';
5
4
  import '../render-response.interface-ClWJXKL4.mjs';
5
+ import 'http';
6
6
  import 'vite';
7
7
  import '@nestjs/core';
8
8
  import 'rxjs';
@@ -1,8 +1,8 @@
1
- export { E as ErrorPageDevelopment, e as ErrorPageProduction, b as RenderInterceptor, R as RenderModule, a as RenderService, S as StreamingErrorHandler, T as TemplateParserService } from '../index-Dq2qZSge.js';
1
+ export { E as ErrorPageDevelopment, a as ErrorPageProduction, b as RenderInterceptor, c as RenderModule, d as RenderService, e as StreamingErrorHandler, T as TemplateParserService } from '../index-CGfEDKI4.js';
2
2
  import '@nestjs/common';
3
3
  import 'react';
4
- import 'express';
5
4
  import '../render-response.interface-ClWJXKL4.js';
5
+ import 'http';
6
6
  import 'vite';
7
7
  import '@nestjs/core';
8
8
  import 'rxjs';
@@ -461,6 +461,39 @@ function ErrorPageProduction() {
461
461
  }
462
462
  __name(ErrorPageProduction, "ErrorPageProduction");
463
463
 
464
+ // src/render/adapters/http-adapter-utils.ts
465
+ function detectAdapterType(httpAdapterHost) {
466
+ const adapter = httpAdapterHost?.httpAdapter;
467
+ if (!adapter) return "unknown";
468
+ const instance = adapter.getInstance();
469
+ if (instance && typeof instance.register === "function") {
470
+ return "fastify";
471
+ }
472
+ if (instance && typeof instance.use === "function" && typeof instance.get === "function") {
473
+ return "express";
474
+ }
475
+ return "unknown";
476
+ }
477
+ __name(detectAdapterType, "detectAdapterType");
478
+ function isFastifyLikeResponse(res) {
479
+ return res != null && typeof res === "object" && "raw" in res && res.raw != null && typeof res.raw.write === "function";
480
+ }
481
+ __name(isFastifyLikeResponse, "isFastifyLikeResponse");
482
+ function getRawResponse(res) {
483
+ if (isFastifyLikeResponse(res)) {
484
+ return res.raw;
485
+ }
486
+ return res;
487
+ }
488
+ __name(getRawResponse, "getRawResponse");
489
+ function isHeadersSent(res) {
490
+ if (typeof res.sent === "boolean") {
491
+ return res.sent;
492
+ }
493
+ return res.headersSent === true;
494
+ }
495
+ __name(isHeadersSent, "isHeadersSent");
496
+
464
497
  // src/render/streaming-error-handler.ts
465
498
  function _ts_decorate3(decorators, target, key, desc) {
466
499
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -496,21 +529,19 @@ exports.StreamingErrorHandler = class _StreamingErrorHandler {
496
529
  */
497
530
  handleShellError(error, res, viewPath, isDevelopment) {
498
531
  this.logger.error(`Shell error rendering ${viewPath}: ${error.message}`, error.stack);
499
- if (res.headersSent) {
532
+ const rawRes = getRawResponse(res);
533
+ if (isHeadersSent(res)) {
500
534
  this.logger.error(`Cannot send error page for ${viewPath} - headers already sent (streaming started)`);
501
- if (!res.writableEnded) {
502
- res.write(this.renderInlineErrorOverlay(error, viewPath, isDevelopment));
503
- res.end();
535
+ if (!rawRes.writableEnded) {
536
+ rawRes.write(this.renderInlineErrorOverlay(error, viewPath, isDevelopment));
537
+ rawRes.end();
504
538
  }
505
539
  return;
506
540
  }
507
- res.statusCode = 500;
508
- res.setHeader("Content-Type", "text/html; charset=utf-8");
509
- if (isDevelopment) {
510
- res.send(this.renderDevelopmentErrorPage(error, viewPath, "shell"));
511
- } else {
512
- res.send(this.renderProductionErrorPage());
513
- }
541
+ rawRes.statusCode = 500;
542
+ rawRes.setHeader("Content-Type", "text/html; charset=utf-8");
543
+ const html = isDevelopment ? this.renderDevelopmentErrorPage(error, viewPath, "shell") : this.renderProductionErrorPage();
544
+ rawRes.end(html);
514
545
  }
515
546
  /**
516
547
  * Handle error that occurred during streaming
@@ -666,7 +697,7 @@ var StreamRenderer = class _StreamRenderer {
666
697
  *
667
698
  * @param viewComponent - The React component to render
668
699
  * @param data - Data to pass to the component
669
- * @param res - Express response object (required for streaming)
700
+ * @param res - HTTP response object (Express or Fastify)
670
701
  * @param context - Render context with Vite and manifest info
671
702
  * @param head - Head data for SEO tags
672
703
  */
@@ -708,20 +739,21 @@ var StreamRenderer = class _StreamRenderer {
708
739
  const { PassThrough } = await import('stream');
709
740
  const reactStream = new PassThrough();
710
741
  let allReadyFired = false;
742
+ const rawRes = getRawResponse(res);
711
743
  const { pipe, abort } = renderModule.renderComponentStream(viewComponent, data, {
712
744
  onShellReady: /* @__PURE__ */ __name(() => {
713
745
  shellReadyTime = Date.now();
714
- if (!res.headersSent) {
715
- res.statusCode = didError ? 500 : 200;
716
- res.setHeader("Content-Type", "text/html; charset=utf-8");
746
+ if (!rawRes.headersSent) {
747
+ rawRes.statusCode = didError ? 500 : 200;
748
+ rawRes.setHeader("Content-Type", "text/html; charset=utf-8");
717
749
  }
718
750
  let htmlStart = templateParts.htmlStart;
719
751
  htmlStart = htmlStart.replace("<!--styles-->", stylesheetTags);
720
752
  htmlStart = htmlStart.replace("<!--head-meta-->", headTags);
721
- res.write(htmlStart);
722
- res.write(templateParts.rootStart);
753
+ rawRes.write(htmlStart);
754
+ rawRes.write(templateParts.rootStart);
723
755
  pipe(reactStream);
724
- reactStream.pipe(res, {
756
+ reactStream.pipe(rawRes, {
725
757
  end: false
726
758
  });
727
759
  if (context.isDevelopment) {
@@ -746,11 +778,11 @@ var StreamRenderer = class _StreamRenderer {
746
778
  if (shellErrorOccurred) {
747
779
  return;
748
780
  }
749
- res.write(inlineScripts);
750
- res.write(clientScript);
751
- res.write(templateParts.rootEnd);
752
- res.write(templateParts.htmlEnd);
753
- res.end();
781
+ rawRes.write(inlineScripts);
782
+ rawRes.write(clientScript);
783
+ rawRes.write(templateParts.rootEnd);
784
+ rawRes.write(templateParts.htmlEnd);
785
+ rawRes.end();
754
786
  if (context.isDevelopment) {
755
787
  const totalTime = Date.now() - startTime;
756
788
  const streamTime = Date.now() - shellReadyTime;
@@ -762,7 +794,7 @@ var StreamRenderer = class _StreamRenderer {
762
794
  reactStream.on("error", (error) => {
763
795
  reject(error);
764
796
  });
765
- res.on("close", () => {
797
+ rawRes.on("close", () => {
766
798
  abort();
767
799
  resolve();
768
800
  });
@@ -1202,9 +1234,10 @@ exports.RenderInterceptor = class RenderInterceptor {
1202
1234
  if (typeof data === "string") {
1203
1235
  return data;
1204
1236
  }
1237
+ const requestPath = request.path ?? request.url?.split("?")[0] ?? "/";
1205
1238
  const renderContext = {
1206
1239
  url: request.url,
1207
- path: request.path,
1240
+ path: requestPath,
1208
1241
  query: request.query,
1209
1242
  params: request.params,
1210
1243
  method: request.method
@@ -1342,7 +1375,7 @@ var ViteInitializerService = class _ViteInitializerService {
1342
1375
  if (isDevelopment) {
1343
1376
  await this.setupDevelopmentMode();
1344
1377
  } else {
1345
- this.setupProductionMode();
1378
+ await this.setupProductionMode();
1346
1379
  }
1347
1380
  }
1348
1381
  async setupDevelopmentMode() {
@@ -1384,18 +1417,38 @@ var ViteInitializerService = class _ViteInitializerService {
1384
1417
  this.logger.warn(`Failed to setup Vite proxy: ${error.message}. Make sure http-proxy-middleware is installed.`);
1385
1418
  }
1386
1419
  }
1387
- setupProductionMode() {
1420
+ async setupProductionMode() {
1388
1421
  try {
1389
1422
  const httpAdapter = this.httpAdapterHost.httpAdapter;
1390
- if (httpAdapter) {
1391
- const app = httpAdapter.getInstance();
1392
- const { join: join2 } = __require("path");
1423
+ if (!httpAdapter) return;
1424
+ const app = httpAdapter.getInstance();
1425
+ const { join: join2 } = __require("path");
1426
+ const staticPath = join2(process.cwd(), "dist/client");
1427
+ const adapterType = detectAdapterType(this.httpAdapterHost);
1428
+ if (adapterType === "fastify") {
1429
+ try {
1430
+ const fastifyStatic = await import('@fastify/static').catch(() => null);
1431
+ if (fastifyStatic) {
1432
+ await app.register(fastifyStatic.default, {
1433
+ root: staticPath,
1434
+ prefix: "/",
1435
+ index: false,
1436
+ maxAge: 31536e6
1437
+ });
1438
+ this.logger.log("\u2713 Static assets configured (dist/client) [Fastify]");
1439
+ } else {
1440
+ this.logger.warn("For Fastify static file serving, install @fastify/static: npm install @fastify/static");
1441
+ }
1442
+ } catch {
1443
+ this.logger.warn("For Fastify static file serving, install @fastify/static: npm install @fastify/static");
1444
+ }
1445
+ } else {
1393
1446
  const express = __require("express");
1394
- app.use(express.static(join2(process.cwd(), "dist/client"), {
1447
+ app.use(express.static(staticPath, {
1395
1448
  index: false,
1396
1449
  maxAge: "1y"
1397
1450
  }));
1398
- this.logger.log("\u2713 Static assets configured (dist/client)");
1451
+ this.logger.log("\u2713 Static assets configured (dist/client) [Express]");
1399
1452
  }
1400
1453
  } catch (error) {
1401
1454
  this.logger.warn(`Failed to setup static assets: ${error.message}`);
@@ -455,6 +455,39 @@ function ErrorPageProduction() {
455
455
  }
456
456
  __name(ErrorPageProduction, "ErrorPageProduction");
457
457
 
458
+ // src/render/adapters/http-adapter-utils.ts
459
+ function detectAdapterType(httpAdapterHost) {
460
+ const adapter = httpAdapterHost?.httpAdapter;
461
+ if (!adapter) return "unknown";
462
+ const instance = adapter.getInstance();
463
+ if (instance && typeof instance.register === "function") {
464
+ return "fastify";
465
+ }
466
+ if (instance && typeof instance.use === "function" && typeof instance.get === "function") {
467
+ return "express";
468
+ }
469
+ return "unknown";
470
+ }
471
+ __name(detectAdapterType, "detectAdapterType");
472
+ function isFastifyLikeResponse(res) {
473
+ return res != null && typeof res === "object" && "raw" in res && res.raw != null && typeof res.raw.write === "function";
474
+ }
475
+ __name(isFastifyLikeResponse, "isFastifyLikeResponse");
476
+ function getRawResponse(res) {
477
+ if (isFastifyLikeResponse(res)) {
478
+ return res.raw;
479
+ }
480
+ return res;
481
+ }
482
+ __name(getRawResponse, "getRawResponse");
483
+ function isHeadersSent(res) {
484
+ if (typeof res.sent === "boolean") {
485
+ return res.sent;
486
+ }
487
+ return res.headersSent === true;
488
+ }
489
+ __name(isHeadersSent, "isHeadersSent");
490
+
458
491
  // src/render/streaming-error-handler.ts
459
492
  function _ts_decorate3(decorators, target, key, desc) {
460
493
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -490,21 +523,19 @@ var StreamingErrorHandler = class _StreamingErrorHandler {
490
523
  */
491
524
  handleShellError(error, res, viewPath, isDevelopment) {
492
525
  this.logger.error(`Shell error rendering ${viewPath}: ${error.message}`, error.stack);
493
- if (res.headersSent) {
526
+ const rawRes = getRawResponse(res);
527
+ if (isHeadersSent(res)) {
494
528
  this.logger.error(`Cannot send error page for ${viewPath} - headers already sent (streaming started)`);
495
- if (!res.writableEnded) {
496
- res.write(this.renderInlineErrorOverlay(error, viewPath, isDevelopment));
497
- res.end();
529
+ if (!rawRes.writableEnded) {
530
+ rawRes.write(this.renderInlineErrorOverlay(error, viewPath, isDevelopment));
531
+ rawRes.end();
498
532
  }
499
533
  return;
500
534
  }
501
- res.statusCode = 500;
502
- res.setHeader("Content-Type", "text/html; charset=utf-8");
503
- if (isDevelopment) {
504
- res.send(this.renderDevelopmentErrorPage(error, viewPath, "shell"));
505
- } else {
506
- res.send(this.renderProductionErrorPage());
507
- }
535
+ rawRes.statusCode = 500;
536
+ rawRes.setHeader("Content-Type", "text/html; charset=utf-8");
537
+ const html = isDevelopment ? this.renderDevelopmentErrorPage(error, viewPath, "shell") : this.renderProductionErrorPage();
538
+ rawRes.end(html);
508
539
  }
509
540
  /**
510
541
  * Handle error that occurred during streaming
@@ -660,7 +691,7 @@ var StreamRenderer = class _StreamRenderer {
660
691
  *
661
692
  * @param viewComponent - The React component to render
662
693
  * @param data - Data to pass to the component
663
- * @param res - Express response object (required for streaming)
694
+ * @param res - HTTP response object (Express or Fastify)
664
695
  * @param context - Render context with Vite and manifest info
665
696
  * @param head - Head data for SEO tags
666
697
  */
@@ -702,20 +733,21 @@ var StreamRenderer = class _StreamRenderer {
702
733
  const { PassThrough } = await import('stream');
703
734
  const reactStream = new PassThrough();
704
735
  let allReadyFired = false;
736
+ const rawRes = getRawResponse(res);
705
737
  const { pipe, abort } = renderModule.renderComponentStream(viewComponent, data, {
706
738
  onShellReady: /* @__PURE__ */ __name(() => {
707
739
  shellReadyTime = Date.now();
708
- if (!res.headersSent) {
709
- res.statusCode = didError ? 500 : 200;
710
- res.setHeader("Content-Type", "text/html; charset=utf-8");
740
+ if (!rawRes.headersSent) {
741
+ rawRes.statusCode = didError ? 500 : 200;
742
+ rawRes.setHeader("Content-Type", "text/html; charset=utf-8");
711
743
  }
712
744
  let htmlStart = templateParts.htmlStart;
713
745
  htmlStart = htmlStart.replace("<!--styles-->", stylesheetTags);
714
746
  htmlStart = htmlStart.replace("<!--head-meta-->", headTags);
715
- res.write(htmlStart);
716
- res.write(templateParts.rootStart);
747
+ rawRes.write(htmlStart);
748
+ rawRes.write(templateParts.rootStart);
717
749
  pipe(reactStream);
718
- reactStream.pipe(res, {
750
+ reactStream.pipe(rawRes, {
719
751
  end: false
720
752
  });
721
753
  if (context.isDevelopment) {
@@ -740,11 +772,11 @@ var StreamRenderer = class _StreamRenderer {
740
772
  if (shellErrorOccurred) {
741
773
  return;
742
774
  }
743
- res.write(inlineScripts);
744
- res.write(clientScript);
745
- res.write(templateParts.rootEnd);
746
- res.write(templateParts.htmlEnd);
747
- res.end();
775
+ rawRes.write(inlineScripts);
776
+ rawRes.write(clientScript);
777
+ rawRes.write(templateParts.rootEnd);
778
+ rawRes.write(templateParts.htmlEnd);
779
+ rawRes.end();
748
780
  if (context.isDevelopment) {
749
781
  const totalTime = Date.now() - startTime;
750
782
  const streamTime = Date.now() - shellReadyTime;
@@ -756,7 +788,7 @@ var StreamRenderer = class _StreamRenderer {
756
788
  reactStream.on("error", (error) => {
757
789
  reject(error);
758
790
  });
759
- res.on("close", () => {
791
+ rawRes.on("close", () => {
760
792
  abort();
761
793
  resolve();
762
794
  });
@@ -1196,9 +1228,10 @@ var RenderInterceptor = class {
1196
1228
  if (typeof data === "string") {
1197
1229
  return data;
1198
1230
  }
1231
+ const requestPath = request.path ?? request.url?.split("?")[0] ?? "/";
1199
1232
  const renderContext = {
1200
1233
  url: request.url,
1201
- path: request.path,
1234
+ path: requestPath,
1202
1235
  query: request.query,
1203
1236
  params: request.params,
1204
1237
  method: request.method
@@ -1336,7 +1369,7 @@ var ViteInitializerService = class _ViteInitializerService {
1336
1369
  if (isDevelopment) {
1337
1370
  await this.setupDevelopmentMode();
1338
1371
  } else {
1339
- this.setupProductionMode();
1372
+ await this.setupProductionMode();
1340
1373
  }
1341
1374
  }
1342
1375
  async setupDevelopmentMode() {
@@ -1378,18 +1411,38 @@ var ViteInitializerService = class _ViteInitializerService {
1378
1411
  this.logger.warn(`Failed to setup Vite proxy: ${error.message}. Make sure http-proxy-middleware is installed.`);
1379
1412
  }
1380
1413
  }
1381
- setupProductionMode() {
1414
+ async setupProductionMode() {
1382
1415
  try {
1383
1416
  const httpAdapter = this.httpAdapterHost.httpAdapter;
1384
- if (httpAdapter) {
1385
- const app = httpAdapter.getInstance();
1386
- const { join: join2 } = __require("path");
1417
+ if (!httpAdapter) return;
1418
+ const app = httpAdapter.getInstance();
1419
+ const { join: join2 } = __require("path");
1420
+ const staticPath = join2(process.cwd(), "dist/client");
1421
+ const adapterType = detectAdapterType(this.httpAdapterHost);
1422
+ if (adapterType === "fastify") {
1423
+ try {
1424
+ const fastifyStatic = await import('@fastify/static').catch(() => null);
1425
+ if (fastifyStatic) {
1426
+ await app.register(fastifyStatic.default, {
1427
+ root: staticPath,
1428
+ prefix: "/",
1429
+ index: false,
1430
+ maxAge: 31536e6
1431
+ });
1432
+ this.logger.log("\u2713 Static assets configured (dist/client) [Fastify]");
1433
+ } else {
1434
+ this.logger.warn("For Fastify static file serving, install @fastify/static: npm install @fastify/static");
1435
+ }
1436
+ } catch {
1437
+ this.logger.warn("For Fastify static file serving, install @fastify/static: npm install @fastify/static");
1438
+ }
1439
+ } else {
1387
1440
  const express = __require("express");
1388
- app.use(express.static(join2(process.cwd(), "dist/client"), {
1441
+ app.use(express.static(staticPath, {
1389
1442
  index: false,
1390
1443
  maxAge: "1y"
1391
1444
  }));
1392
- this.logger.log("\u2713 Static assets configured (dist/client)");
1445
+ this.logger.log("\u2713 Static assets configured (dist/client) [Express]");
1393
1446
  }
1394
1447
  } catch (error) {
1395
1448
  this.logger.warn(`Failed to setup static assets: ${error.message}`);
@@ -279,4 +279,4 @@ declare const useHeader: (name: string) => string | undefined;
279
279
  declare const useCookies: () => Record<string, string>;
280
280
  declare const useCookie: (name: string) => string | undefined;
281
281
 
282
- export { type PageProps as P, PageContextProvider as a, useParams as b, createSSRHooks as c, useQuery as d, useRequest as e, useHeaders as f, useHeader as g, useCookies as h, useCookie as i, updatePageContext as j, usePageContext as u };
282
+ export { type PageProps as P, PageContextProvider as a, useCookies as b, createSSRHooks as c, useHeader as d, useHeaders as e, usePageContext as f, useParams as g, useQuery as h, useRequest as i, updatePageContext as j, useCookie as u };
@@ -279,4 +279,4 @@ declare const useHeader: (name: string) => string | undefined;
279
279
  declare const useCookies: () => Record<string, string>;
280
280
  declare const useCookie: (name: string) => string | undefined;
281
281
 
282
- export { type PageProps as P, PageContextProvider as a, useParams as b, createSSRHooks as c, useQuery as d, useRequest as e, useHeaders as f, useHeader as g, useCookies as h, useCookie as i, updatePageContext as j, usePageContext as u };
282
+ export { type PageProps as P, PageContextProvider as a, useCookies as b, createSSRHooks as c, useHeader as d, useHeaders as e, usePageContext as f, useParams as g, useQuery as h, useRequest as i, updatePageContext as j, useCookie as u };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nestjs-ssr/react",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "React SSR for NestJS that respects Clean Architecture. Proper DI, SOLID principles, clear separation of concerns.",
5
5
  "keywords": [
6
6
  "nestjs",
@@ -109,9 +109,11 @@
109
109
  }
110
110
  ],
111
111
  "peerDependencies": {
112
+ "@fastify/static": "^8.0.0 || ^7.0.0",
112
113
  "@nestjs/common": "^11.0.0",
113
114
  "@nestjs/core": "^11.0.0",
114
115
  "@nestjs/platform-express": "^11.0.0",
116
+ "@nestjs/platform-fastify": "^11.0.0",
115
117
  "http-proxy-middleware": "^3.0.0 || ^2.0.0",
116
118
  "react": "^19.0.0",
117
119
  "react-dom": "^19.0.0",
@@ -119,43 +121,52 @@
119
121
  "vite": "^7.0.0 || ^6.0.0"
120
122
  },
121
123
  "peerDependenciesMeta": {
124
+ "@nestjs/platform-express": {
125
+ "optional": true
126
+ },
127
+ "@nestjs/platform-fastify": {
128
+ "optional": true
129
+ },
130
+ "@fastify/static": {
131
+ "optional": true
132
+ },
122
133
  "http-proxy-middleware": {
123
134
  "optional": true
124
135
  }
125
136
  },
126
137
  "dependencies": {
127
- "citty": "^0.1.6",
138
+ "citty": "^0.2.0",
128
139
  "consola": "^3.4.2",
129
- "devalue": "^5.6.1",
140
+ "devalue": "^5.6.2",
130
141
  "escape-html": "^1.0.3"
131
142
  },
132
143
  "devDependencies": {
133
- "@microsoft/api-extractor": "^7.55.2",
134
- "@nestjs/common": "^11.1.9",
135
- "@nestjs/core": "^11.1.9",
136
- "@nestjs/platform-express": "^11.1.9",
137
- "@nestjs/testing": "^11.1.9",
138
- "@playwright/test": "^1.57.0",
144
+ "@microsoft/api-extractor": "^7.56.2",
145
+ "@nestjs/common": "^11.1.13",
146
+ "@nestjs/core": "^11.1.13",
147
+ "@nestjs/platform-express": "^11.1.13",
148
+ "@nestjs/testing": "^11.1.13",
149
+ "@playwright/test": "^1.58.1",
139
150
  "@testing-library/jest-dom": "^6.9.1",
140
- "@testing-library/react": "^16.3.0",
151
+ "@testing-library/react": "^16.3.2",
141
152
  "@types/escape-html": "^1.0.4",
142
153
  "@types/express": "^5.0.6",
143
- "@types/node": "^25.0.1",
144
- "@types/react": "^19.2.7",
154
+ "@types/node": "^25.2.1",
155
+ "@types/react": "^19.2.13",
145
156
  "@types/react-dom": "^19.2.3",
146
157
  "@types/supertest": "^6.0.3",
147
- "@vitejs/plugin-react": "^5.1.2",
148
- "@vitest/coverage-v8": "^4.0.15",
149
- "@vitest/ui": "^4.0.15",
150
- "happy-dom": "^20.0.11",
151
- "react": "^19.2.3",
152
- "react-dom": "^19.2.3",
158
+ "@vitejs/plugin-react": "^5.1.3",
159
+ "@vitest/coverage-v8": "^4.0.18",
160
+ "@vitest/ui": "^4.0.18",
161
+ "happy-dom": "^20.5.0",
162
+ "react": "^19.2.4",
163
+ "react-dom": "^19.2.4",
153
164
  "rxjs": "^7.8.2",
154
- "supertest": "^7.1.4",
165
+ "supertest": "^7.2.2",
155
166
  "tsup": "^8.5.1",
156
167
  "typescript": "^5.9.3",
157
- "vite": "^7.2.7",
158
- "vitest": "^4.0.15"
168
+ "vite": "^7.3.1",
169
+ "vitest": "^4.0.18"
159
170
  },
160
171
  "publishConfig": {
161
172
  "access": "public"