@nestjs-ssr/react 0.3.5 → 0.3.6

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.
@@ -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).
@@ -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).
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, 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-ZpkYrPcK.mjs';
2
2
  import React, { ComponentType, ReactNode } from 'react';
3
3
  import { P as PageProps } from './use-page-context-CVC9DHcL.mjs';
4
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';
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, 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-CSvZfKpi.js';
2
2
  import React, { ComponentType, ReactNode } from 'react';
3
3
  import { P as PageProps } from './use-page-context-DChgHhL9.js';
4
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';
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
  });
@@ -1361,7 +1393,7 @@ var ViteInitializerService = class _ViteInitializerService {
1361
1393
  if (isDevelopment) {
1362
1394
  await this.setupDevelopmentMode();
1363
1395
  } else {
1364
- this.setupProductionMode();
1396
+ await this.setupProductionMode();
1365
1397
  }
1366
1398
  }
1367
1399
  async setupDevelopmentMode() {
@@ -1403,18 +1435,38 @@ var ViteInitializerService = class _ViteInitializerService {
1403
1435
  this.logger.warn(`Failed to setup Vite proxy: ${error.message}. Make sure http-proxy-middleware is installed.`);
1404
1436
  }
1405
1437
  }
1406
- setupProductionMode() {
1438
+ async setupProductionMode() {
1407
1439
  try {
1408
1440
  const httpAdapter = this.httpAdapterHost.httpAdapter;
1409
- if (httpAdapter) {
1410
- const app = httpAdapter.getInstance();
1411
- const { join: join2 } = __require("path");
1441
+ if (!httpAdapter) return;
1442
+ const app = httpAdapter.getInstance();
1443
+ const { join: join2 } = __require("path");
1444
+ const staticPath = join2(process.cwd(), "dist/client");
1445
+ const adapterType = detectAdapterType(this.httpAdapterHost);
1446
+ if (adapterType === "fastify") {
1447
+ try {
1448
+ const fastifyStatic = await import('@fastify/static').catch(() => null);
1449
+ if (fastifyStatic) {
1450
+ await app.register(fastifyStatic.default, {
1451
+ root: staticPath,
1452
+ prefix: "/",
1453
+ index: false,
1454
+ maxAge: 31536e6
1455
+ });
1456
+ this.logger.log("\u2713 Static assets configured (dist/client) [Fastify]");
1457
+ } else {
1458
+ this.logger.warn("For Fastify static file serving, install @fastify/static: npm install @fastify/static");
1459
+ }
1460
+ } catch {
1461
+ this.logger.warn("For Fastify static file serving, install @fastify/static: npm install @fastify/static");
1462
+ }
1463
+ } else {
1412
1464
  const express = __require("express");
1413
- app.use(express.static(join2(process.cwd(), "dist/client"), {
1465
+ app.use(express.static(staticPath, {
1414
1466
  index: false,
1415
1467
  maxAge: "1y"
1416
1468
  }));
1417
- this.logger.log("\u2713 Static assets configured (dist/client)");
1469
+ this.logger.log("\u2713 Static assets configured (dist/client) [Express]");
1418
1470
  }
1419
1471
  } catch (error) {
1420
1472
  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
  });
@@ -1354,7 +1386,7 @@ var ViteInitializerService = class _ViteInitializerService {
1354
1386
  if (isDevelopment) {
1355
1387
  await this.setupDevelopmentMode();
1356
1388
  } else {
1357
- this.setupProductionMode();
1389
+ await this.setupProductionMode();
1358
1390
  }
1359
1391
  }
1360
1392
  async setupDevelopmentMode() {
@@ -1396,18 +1428,38 @@ var ViteInitializerService = class _ViteInitializerService {
1396
1428
  this.logger.warn(`Failed to setup Vite proxy: ${error.message}. Make sure http-proxy-middleware is installed.`);
1397
1429
  }
1398
1430
  }
1399
- setupProductionMode() {
1431
+ async setupProductionMode() {
1400
1432
  try {
1401
1433
  const httpAdapter = this.httpAdapterHost.httpAdapter;
1402
- if (httpAdapter) {
1403
- const app = httpAdapter.getInstance();
1404
- const { join: join2 } = __require("path");
1434
+ if (!httpAdapter) return;
1435
+ const app = httpAdapter.getInstance();
1436
+ const { join: join2 } = __require("path");
1437
+ const staticPath = join2(process.cwd(), "dist/client");
1438
+ const adapterType = detectAdapterType(this.httpAdapterHost);
1439
+ if (adapterType === "fastify") {
1440
+ try {
1441
+ const fastifyStatic = await import('@fastify/static').catch(() => null);
1442
+ if (fastifyStatic) {
1443
+ await app.register(fastifyStatic.default, {
1444
+ root: staticPath,
1445
+ prefix: "/",
1446
+ index: false,
1447
+ maxAge: 31536e6
1448
+ });
1449
+ this.logger.log("\u2713 Static assets configured (dist/client) [Fastify]");
1450
+ } else {
1451
+ this.logger.warn("For Fastify static file serving, install @fastify/static: npm install @fastify/static");
1452
+ }
1453
+ } catch {
1454
+ this.logger.warn("For Fastify static file serving, install @fastify/static: npm install @fastify/static");
1455
+ }
1456
+ } else {
1405
1457
  const express = __require("express");
1406
- app.use(express.static(join2(process.cwd(), "dist/client"), {
1458
+ app.use(express.static(staticPath, {
1407
1459
  index: false,
1408
1460
  maxAge: "1y"
1409
1461
  }));
1410
- this.logger.log("\u2713 Static assets configured (dist/client)");
1462
+ this.logger.log("\u2713 Static assets configured (dist/client) [Express]");
1411
1463
  }
1412
1464
  } catch (error) {
1413
1465
  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, e as ErrorPageProduction, b as RenderInterceptor, R as RenderModule, a as RenderService, S as StreamingErrorHandler, T as TemplateParserService } from '../index-ZpkYrPcK.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, e as ErrorPageProduction, b as RenderInterceptor, R as RenderModule, a as RenderService, S as StreamingErrorHandler, T as TemplateParserService } from '../index-CSvZfKpi.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
  });
@@ -1342,7 +1374,7 @@ var ViteInitializerService = class _ViteInitializerService {
1342
1374
  if (isDevelopment) {
1343
1375
  await this.setupDevelopmentMode();
1344
1376
  } else {
1345
- this.setupProductionMode();
1377
+ await this.setupProductionMode();
1346
1378
  }
1347
1379
  }
1348
1380
  async setupDevelopmentMode() {
@@ -1384,18 +1416,38 @@ var ViteInitializerService = class _ViteInitializerService {
1384
1416
  this.logger.warn(`Failed to setup Vite proxy: ${error.message}. Make sure http-proxy-middleware is installed.`);
1385
1417
  }
1386
1418
  }
1387
- setupProductionMode() {
1419
+ async setupProductionMode() {
1388
1420
  try {
1389
1421
  const httpAdapter = this.httpAdapterHost.httpAdapter;
1390
- if (httpAdapter) {
1391
- const app = httpAdapter.getInstance();
1392
- const { join: join2 } = __require("path");
1422
+ if (!httpAdapter) return;
1423
+ const app = httpAdapter.getInstance();
1424
+ const { join: join2 } = __require("path");
1425
+ const staticPath = join2(process.cwd(), "dist/client");
1426
+ const adapterType = detectAdapterType(this.httpAdapterHost);
1427
+ if (adapterType === "fastify") {
1428
+ try {
1429
+ const fastifyStatic = await import('@fastify/static').catch(() => null);
1430
+ if (fastifyStatic) {
1431
+ await app.register(fastifyStatic.default, {
1432
+ root: staticPath,
1433
+ prefix: "/",
1434
+ index: false,
1435
+ maxAge: 31536e6
1436
+ });
1437
+ this.logger.log("\u2713 Static assets configured (dist/client) [Fastify]");
1438
+ } else {
1439
+ this.logger.warn("For Fastify static file serving, install @fastify/static: npm install @fastify/static");
1440
+ }
1441
+ } catch {
1442
+ this.logger.warn("For Fastify static file serving, install @fastify/static: npm install @fastify/static");
1443
+ }
1444
+ } else {
1393
1445
  const express = __require("express");
1394
- app.use(express.static(join2(process.cwd(), "dist/client"), {
1446
+ app.use(express.static(staticPath, {
1395
1447
  index: false,
1396
1448
  maxAge: "1y"
1397
1449
  }));
1398
- this.logger.log("\u2713 Static assets configured (dist/client)");
1450
+ this.logger.log("\u2713 Static assets configured (dist/client) [Express]");
1399
1451
  }
1400
1452
  } catch (error) {
1401
1453
  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
  });
@@ -1336,7 +1368,7 @@ var ViteInitializerService = class _ViteInitializerService {
1336
1368
  if (isDevelopment) {
1337
1369
  await this.setupDevelopmentMode();
1338
1370
  } else {
1339
- this.setupProductionMode();
1371
+ await this.setupProductionMode();
1340
1372
  }
1341
1373
  }
1342
1374
  async setupDevelopmentMode() {
@@ -1378,18 +1410,38 @@ var ViteInitializerService = class _ViteInitializerService {
1378
1410
  this.logger.warn(`Failed to setup Vite proxy: ${error.message}. Make sure http-proxy-middleware is installed.`);
1379
1411
  }
1380
1412
  }
1381
- setupProductionMode() {
1413
+ async setupProductionMode() {
1382
1414
  try {
1383
1415
  const httpAdapter = this.httpAdapterHost.httpAdapter;
1384
- if (httpAdapter) {
1385
- const app = httpAdapter.getInstance();
1386
- const { join: join2 } = __require("path");
1416
+ if (!httpAdapter) return;
1417
+ const app = httpAdapter.getInstance();
1418
+ const { join: join2 } = __require("path");
1419
+ const staticPath = join2(process.cwd(), "dist/client");
1420
+ const adapterType = detectAdapterType(this.httpAdapterHost);
1421
+ if (adapterType === "fastify") {
1422
+ try {
1423
+ const fastifyStatic = await import('@fastify/static').catch(() => null);
1424
+ if (fastifyStatic) {
1425
+ await app.register(fastifyStatic.default, {
1426
+ root: staticPath,
1427
+ prefix: "/",
1428
+ index: false,
1429
+ maxAge: 31536e6
1430
+ });
1431
+ this.logger.log("\u2713 Static assets configured (dist/client) [Fastify]");
1432
+ } else {
1433
+ this.logger.warn("For Fastify static file serving, install @fastify/static: npm install @fastify/static");
1434
+ }
1435
+ } catch {
1436
+ this.logger.warn("For Fastify static file serving, install @fastify/static: npm install @fastify/static");
1437
+ }
1438
+ } else {
1387
1439
  const express = __require("express");
1388
- app.use(express.static(join2(process.cwd(), "dist/client"), {
1440
+ app.use(express.static(staticPath, {
1389
1441
  index: false,
1390
1442
  maxAge: "1y"
1391
1443
  }));
1392
- this.logger.log("\u2713 Static assets configured (dist/client)");
1444
+ this.logger.log("\u2713 Static assets configured (dist/client) [Express]");
1393
1445
  }
1394
1446
  } catch (error) {
1395
1447
  this.logger.warn(`Failed to setup static assets: ${error.message}`);
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.6",
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",
@@ -112,6 +112,8 @@
112
112
  "@nestjs/common": "^11.0.0",
113
113
  "@nestjs/core": "^11.0.0",
114
114
  "@nestjs/platform-express": "^11.0.0",
115
+ "@nestjs/platform-fastify": "^11.0.0",
116
+ "@fastify/static": "^8.0.0 || ^7.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,6 +121,15 @@
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
  }