@nestjs-ssr/react 0.2.6 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -5,7 +5,7 @@ import { join, relative } from 'path';
5
5
  import { uneval } from 'devalue';
6
6
  import escapeHtml from 'escape-html';
7
7
  import { renderToStaticMarkup } from 'react-dom/server';
8
- import React, { createElement, createContext, useContext } from 'react';
8
+ import React, { createElement, createContext, useContext, useState, useEffect } from 'react';
9
9
  import { switchMap } from 'rxjs/operators';
10
10
 
11
11
  var __defProp = Object.defineProperty;
@@ -185,6 +185,153 @@ window.__LAYOUTS__ = ${uneval(layoutMetadata)};
185
185
  TemplateParserService = _ts_decorate([
186
186
  Injectable()
187
187
  ], TemplateParserService);
188
+
189
+ // src/render/renderers/string-renderer.ts
190
+ function _ts_decorate2(decorators, target, key, desc) {
191
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
192
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
193
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
194
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
195
+ }
196
+ __name(_ts_decorate2, "_ts_decorate");
197
+ function _ts_metadata(k, v) {
198
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
199
+ }
200
+ __name(_ts_metadata, "_ts_metadata");
201
+ var StringRenderer = class _StringRenderer {
202
+ static {
203
+ __name(this, "StringRenderer");
204
+ }
205
+ templateParser;
206
+ logger = new Logger(_StringRenderer.name);
207
+ constructor(templateParser) {
208
+ this.templateParser = templateParser;
209
+ }
210
+ /**
211
+ * Render a React component to a complete HTML string
212
+ */
213
+ async render(viewComponent, data, context, head) {
214
+ const startTime = Date.now();
215
+ let template = context.template;
216
+ if (context.vite) {
217
+ template = await context.vite.transformIndexHtml("/", template);
218
+ }
219
+ let renderModule;
220
+ if (context.vite) {
221
+ renderModule = await context.vite.ssrLoadModule(context.entryServerPath);
222
+ } else {
223
+ if (context.serverManifest) {
224
+ const manifestEntry = Object.entries(context.serverManifest).find(([key, value]) => value.isEntry && key.includes("entry-server"));
225
+ if (manifestEntry) {
226
+ const [, entry] = manifestEntry;
227
+ const serverPath = `${process.cwd()}/dist/server/${entry.file}`;
228
+ renderModule = await import(serverPath);
229
+ } else {
230
+ throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
231
+ }
232
+ } else {
233
+ throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
234
+ }
235
+ }
236
+ const { data: pageData, __context: pageContext, __layouts: layouts } = data;
237
+ const appHtml = await renderModule.renderComponent(viewComponent, data);
238
+ const componentName = viewComponent.displayName || viewComponent.name || "Component";
239
+ const layoutMetadata = layouts ? layouts.map((l) => ({
240
+ name: l.layout.displayName || l.layout.name || "default",
241
+ props: l.props
242
+ })) : [];
243
+ const initialStateScript = `
244
+ <script>
245
+ window.__INITIAL_STATE__ = ${uneval(pageData)};
246
+ window.__CONTEXT__ = ${uneval(pageContext)};
247
+ window.__COMPONENT_NAME__ = ${uneval(componentName)};
248
+ window.__LAYOUTS__ = ${uneval(layoutMetadata)};
249
+ </script>
250
+ `;
251
+ let clientScript = "";
252
+ let styles = "";
253
+ if (context.vite) {
254
+ clientScript = `<script type="module" src="/src/views/entry-client.tsx"></script>`;
255
+ } else {
256
+ if (context.manifest) {
257
+ const manifestEntry = Object.entries(context.manifest).find(([key, value]) => value.isEntry && key.includes("entry-client"));
258
+ if (manifestEntry) {
259
+ const [, entry] = manifestEntry;
260
+ const entryFile = entry.file;
261
+ clientScript = `<script type="module" src="/${entryFile}"></script>`;
262
+ if (entry.css) {
263
+ const cssFiles = entry.css;
264
+ styles = cssFiles.map((css) => `<link rel="stylesheet" href="/${css}" />`).join("\n ");
265
+ }
266
+ } else {
267
+ this.logger.error("\u26A0\uFE0F Client entry not found in manifest");
268
+ clientScript = `<script type="module" src="/assets/client.js"></script>`;
269
+ }
270
+ } else {
271
+ this.logger.error("\u26A0\uFE0F Client manifest not found");
272
+ clientScript = `<script type="module" src="/assets/client.js"></script>`;
273
+ }
274
+ }
275
+ const headTags = this.templateParser.buildHeadTags(head);
276
+ let html = template.replace("<!--app-html-->", appHtml);
277
+ html = html.replace("<!--initial-state-->", initialStateScript);
278
+ html = html.replace("<!--client-scripts-->", clientScript);
279
+ html = html.replace("<!--styles-->", styles);
280
+ html = html.replace("<!--head-meta-->", headTags);
281
+ if (context.isDevelopment) {
282
+ const duration = Date.now() - startTime;
283
+ const name = typeof viewComponent === "function" ? viewComponent.name : String(viewComponent);
284
+ this.logger.log(`[SSR] ${name} rendered in ${duration}ms (string mode)`);
285
+ }
286
+ return html;
287
+ }
288
+ /**
289
+ * Render a segment for client-side navigation.
290
+ * Returns just the HTML and metadata without the full page template.
291
+ */
292
+ async renderSegment(viewComponent, data, context, swapTarget, head) {
293
+ const startTime = Date.now();
294
+ let renderModule;
295
+ if (context.vite) {
296
+ renderModule = await context.vite.ssrLoadModule(context.entryServerPath);
297
+ } else {
298
+ if (context.serverManifest) {
299
+ const manifestEntry = Object.entries(context.serverManifest).find(([key, value]) => value.isEntry && key.includes("entry-server"));
300
+ if (manifestEntry) {
301
+ const [, entry] = manifestEntry;
302
+ const serverPath = `${process.cwd()}/dist/server/${entry.file}`;
303
+ renderModule = await import(serverPath);
304
+ } else {
305
+ throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
306
+ }
307
+ } else {
308
+ throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
309
+ }
310
+ }
311
+ const { data: pageData, __context: pageContext } = data;
312
+ const html = await renderModule.renderSegment(viewComponent, data);
313
+ const componentName = viewComponent.displayName || viewComponent.name || "Component";
314
+ if (context.isDevelopment) {
315
+ const duration = Date.now() - startTime;
316
+ this.logger.log(`[SSR] ${componentName} segment rendered in ${duration}ms`);
317
+ }
318
+ return {
319
+ html,
320
+ head,
321
+ props: pageData,
322
+ swapTarget,
323
+ componentName,
324
+ context: pageContext
325
+ };
326
+ }
327
+ };
328
+ StringRenderer = _ts_decorate2([
329
+ Injectable(),
330
+ _ts_metadata("design:type", Function),
331
+ _ts_metadata("design:paramtypes", [
332
+ typeof TemplateParserService === "undefined" ? Object : TemplateParserService
333
+ ])
334
+ ], StringRenderer);
188
335
  function ErrorPageDevelopment({ error, viewPath, phase }) {
189
336
  const stackLines = error.stack ? error.stack.split("\n").slice(1) : [];
190
337
  return /* @__PURE__ */ React.createElement("html", {
@@ -300,17 +447,17 @@ function ErrorPageProduction() {
300
447
  __name(ErrorPageProduction, "ErrorPageProduction");
301
448
 
302
449
  // src/render/streaming-error-handler.ts
303
- function _ts_decorate2(decorators, target, key, desc) {
450
+ function _ts_decorate3(decorators, target, key, desc) {
304
451
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
305
452
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
306
453
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
307
454
  return c > 3 && r && Object.defineProperty(target, key, r), r;
308
455
  }
309
- __name(_ts_decorate2, "_ts_decorate");
310
- function _ts_metadata(k, v) {
456
+ __name(_ts_decorate3, "_ts_decorate");
457
+ function _ts_metadata2(k, v) {
311
458
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
312
459
  }
313
- __name(_ts_metadata, "_ts_metadata");
460
+ __name(_ts_metadata2, "_ts_metadata");
314
461
  function _ts_param(paramIndex, decorator) {
315
462
  return function(target, key) {
316
463
  decorator(target, key, paramIndex);
@@ -334,6 +481,14 @@ var StreamingErrorHandler = class _StreamingErrorHandler {
334
481
  */
335
482
  handleShellError(error, res, viewPath, isDevelopment) {
336
483
  this.logger.error(`Shell error rendering ${viewPath}: ${error.message}`, error.stack);
484
+ if (res.headersSent) {
485
+ this.logger.error(`Cannot send error page for ${viewPath} - headers already sent (streaming started)`);
486
+ if (!res.writableEnded) {
487
+ res.write(this.renderInlineErrorOverlay(error, viewPath, isDevelopment));
488
+ res.end();
489
+ }
490
+ return;
491
+ }
337
492
  res.statusCode = 500;
338
493
  res.setHeader("Content-Type", "text/html; charset=utf-8");
339
494
  if (isDevelopment) {
@@ -369,32 +524,263 @@ var StreamingErrorHandler = class _StreamingErrorHandler {
369
524
  const element = createElement(ErrorComponent);
370
525
  return "<!DOCTYPE html>\n" + renderToStaticMarkup(element);
371
526
  }
527
+ /**
528
+ * Render inline error overlay for when headers are already sent
529
+ * This gets injected into the stream to show a visible error UI
530
+ */
531
+ renderInlineErrorOverlay(error, viewPath, isDevelopment) {
532
+ const errorMessage = escapeHtml(error.message);
533
+ const errorStack = escapeHtml(error.stack || "");
534
+ const escapedViewPath = escapeHtml(viewPath);
535
+ if (isDevelopment) {
536
+ return `
537
+ <div id="ssr-error-overlay" style="
538
+ position: fixed;
539
+ inset: 0;
540
+ z-index: 99999;
541
+ background: rgba(0, 0, 0, 0.85);
542
+ color: #fff;
543
+ font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
544
+ font-size: 14px;
545
+ padding: 32px;
546
+ overflow: auto;
547
+ ">
548
+ <div style="max-width: 900px; margin: 0 auto;">
549
+ <div style="display: flex; align-items: center; gap: 12px; margin-bottom: 24px;">
550
+ <span style="font-size: 32px;">\u26A0\uFE0F</span>
551
+ <h1 style="margin: 0; font-size: 24px; font-weight: 600; color: #ff6b6b;">
552
+ SSR Streaming Error
553
+ </h1>
554
+ </div>
555
+ <p style="color: #aaa; margin-bottom: 16px;">
556
+ An error occurred after streaming started in <code style="background: #333; padding: 2px 6px; border-radius: 4px;">${escapedViewPath}</code>
557
+ </p>
558
+ <div style="background: #1a1a1a; border: 1px solid #333; border-radius: 8px; padding: 16px; margin-bottom: 16px;">
559
+ <div style="color: #ff6b6b; font-weight: 600; margin-bottom: 8px;">Error Message:</div>
560
+ <pre style="margin: 0; white-space: pre-wrap; word-break: break-word; color: #fff;">${errorMessage}</pre>
561
+ </div>
562
+ <div style="background: #1a1a1a; border: 1px solid #333; border-radius: 8px; padding: 16px;">
563
+ <div style="color: #888; font-weight: 600; margin-bottom: 8px;">Stack Trace:</div>
564
+ <pre style="margin: 0; white-space: pre-wrap; word-break: break-word; color: #888; font-size: 12px;">${errorStack}</pre>
565
+ </div>
566
+ <button onclick="document.getElementById('ssr-error-overlay').remove()" style="
567
+ margin-top: 24px;
568
+ background: #333;
569
+ color: #fff;
570
+ border: 1px solid #555;
571
+ padding: 8px 16px;
572
+ border-radius: 6px;
573
+ cursor: pointer;
574
+ font-family: inherit;
575
+ ">Dismiss</button>
576
+ </div>
577
+ </div>
578
+ <script>console.error('SSR Streaming Error in ${escapedViewPath}:', ${uneval(error.message)});</script>
579
+ `;
580
+ } else {
581
+ return `
582
+ <div id="ssr-error-overlay" style="
583
+ position: fixed;
584
+ inset: 0;
585
+ z-index: 99999;
586
+ background: #fff;
587
+ color: #333;
588
+ font-family: system-ui, -apple-system, sans-serif;
589
+ display: flex;
590
+ align-items: center;
591
+ justify-content: center;
592
+ text-align: center;
593
+ ">
594
+ <div>
595
+ <h1 style="font-size: 24px; font-weight: 600; margin-bottom: 16px;">Something went wrong</h1>
596
+ <p style="color: #666; margin-bottom: 24px;">We're sorry, but something went wrong. Please try refreshing the page.</p>
597
+ <button onclick="location.reload()" style="
598
+ background: #333;
599
+ color: #fff;
600
+ border: none;
601
+ padding: 12px 24px;
602
+ border-radius: 6px;
603
+ cursor: pointer;
604
+ font-family: inherit;
605
+ font-size: 16px;
606
+ ">Refresh Page</button>
607
+ </div>
608
+ </div>
609
+ `;
610
+ }
611
+ }
372
612
  };
373
- StreamingErrorHandler = _ts_decorate2([
613
+ StreamingErrorHandler = _ts_decorate3([
374
614
  Injectable(),
375
615
  _ts_param(0, Optional()),
376
616
  _ts_param(0, Inject("ERROR_PAGE_DEVELOPMENT")),
377
617
  _ts_param(1, Optional()),
378
618
  _ts_param(1, Inject("ERROR_PAGE_PRODUCTION")),
379
- _ts_metadata("design:type", Function),
380
- _ts_metadata("design:paramtypes", [
619
+ _ts_metadata2("design:type", Function),
620
+ _ts_metadata2("design:paramtypes", [
381
621
  typeof ComponentType === "undefined" ? Object : ComponentType,
382
622
  typeof ComponentType === "undefined" ? Object : ComponentType
383
623
  ])
384
624
  ], StreamingErrorHandler);
385
625
 
626
+ // src/render/renderers/stream-renderer.ts
627
+ function _ts_decorate4(decorators, target, key, desc) {
628
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
629
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
630
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
631
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
632
+ }
633
+ __name(_ts_decorate4, "_ts_decorate");
634
+ function _ts_metadata3(k, v) {
635
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
636
+ }
637
+ __name(_ts_metadata3, "_ts_metadata");
638
+ var StreamRenderer = class _StreamRenderer {
639
+ static {
640
+ __name(this, "StreamRenderer");
641
+ }
642
+ templateParser;
643
+ streamingErrorHandler;
644
+ logger = new Logger(_StreamRenderer.name);
645
+ constructor(templateParser, streamingErrorHandler) {
646
+ this.templateParser = templateParser;
647
+ this.streamingErrorHandler = streamingErrorHandler;
648
+ }
649
+ /**
650
+ * Render a React component using streaming SSR
651
+ *
652
+ * @param viewComponent - The React component to render
653
+ * @param data - Data to pass to the component
654
+ * @param res - Express response object (required for streaming)
655
+ * @param context - Render context with Vite and manifest info
656
+ * @param head - Head data for SEO tags
657
+ */
658
+ async render(viewComponent, data, res, context, head) {
659
+ const startTime = Date.now();
660
+ let shellReadyTime = 0;
661
+ return new Promise((resolve, reject) => {
662
+ const executeStream = /* @__PURE__ */ __name(async () => {
663
+ let template = context.template;
664
+ if (context.vite) {
665
+ template = await context.vite.transformIndexHtml("/", template);
666
+ }
667
+ const templateParts = this.templateParser.parseTemplate(template);
668
+ let renderModule;
669
+ if (context.vite) {
670
+ renderModule = await context.vite.ssrLoadModule(context.entryServerPath);
671
+ } else {
672
+ if (context.serverManifest) {
673
+ const manifestEntry = Object.entries(context.serverManifest).find(([key, value]) => value.isEntry && key.includes("entry-server"));
674
+ if (manifestEntry) {
675
+ const [, entry] = manifestEntry;
676
+ const serverPath = `${process.cwd()}/dist/server/${entry.file}`;
677
+ renderModule = await import(serverPath);
678
+ } else {
679
+ throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
680
+ }
681
+ } else {
682
+ throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
683
+ }
684
+ }
685
+ const { data: pageData, __context: pageContext, __layouts: layouts } = data;
686
+ const componentName = viewComponent.displayName || viewComponent.name || "Component";
687
+ const inlineScripts = this.templateParser.buildInlineScripts(pageData, pageContext, componentName, layouts);
688
+ const clientScript = this.templateParser.getClientScriptTag(context.isDevelopment, context.manifest);
689
+ const stylesheetTags = this.templateParser.getStylesheetTags(context.isDevelopment, context.manifest);
690
+ const headTags = this.templateParser.buildHeadTags(head);
691
+ let didError = false;
692
+ let shellErrorOccurred = false;
693
+ const { PassThrough } = await import('stream');
694
+ const reactStream = new PassThrough();
695
+ let allReadyFired = false;
696
+ const { pipe, abort } = renderModule.renderComponentStream(viewComponent, data, {
697
+ onShellReady: /* @__PURE__ */ __name(() => {
698
+ shellReadyTime = Date.now();
699
+ if (!res.headersSent) {
700
+ res.statusCode = didError ? 500 : 200;
701
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
702
+ }
703
+ let htmlStart = templateParts.htmlStart;
704
+ htmlStart = htmlStart.replace("<!--styles-->", stylesheetTags);
705
+ htmlStart = htmlStart.replace("<!--head-meta-->", headTags);
706
+ res.write(htmlStart);
707
+ res.write(templateParts.rootStart);
708
+ pipe(reactStream);
709
+ reactStream.pipe(res, {
710
+ end: false
711
+ });
712
+ if (context.isDevelopment) {
713
+ const ttfb = shellReadyTime - startTime;
714
+ this.logger.log(`[SSR] ${componentName} shell ready in ${ttfb}ms (stream mode - TTFB)`);
715
+ }
716
+ }, "onShellReady"),
717
+ onShellError: /* @__PURE__ */ __name((error) => {
718
+ shellErrorOccurred = true;
719
+ this.streamingErrorHandler.handleShellError(error, res, componentName, context.isDevelopment);
720
+ resolve();
721
+ }, "onShellError"),
722
+ onError: /* @__PURE__ */ __name((error) => {
723
+ didError = true;
724
+ this.streamingErrorHandler.handleStreamError(error, componentName);
725
+ }, "onError"),
726
+ onAllReady: /* @__PURE__ */ __name(() => {
727
+ allReadyFired = true;
728
+ }, "onAllReady")
729
+ });
730
+ reactStream.on("end", () => {
731
+ if (shellErrorOccurred) {
732
+ return;
733
+ }
734
+ res.write(inlineScripts);
735
+ res.write(clientScript);
736
+ res.write(templateParts.rootEnd);
737
+ res.write(templateParts.htmlEnd);
738
+ res.end();
739
+ if (context.isDevelopment) {
740
+ const totalTime = Date.now() - startTime;
741
+ const streamTime = Date.now() - shellReadyTime;
742
+ const viaAllReady = allReadyFired ? " (onAllReady fired)" : " (onAllReady never fired)";
743
+ this.logger.log(`[SSR] ${componentName} streaming complete in ${totalTime}ms total (${streamTime}ms streaming)${viaAllReady}`);
744
+ }
745
+ resolve();
746
+ });
747
+ reactStream.on("error", (error) => {
748
+ reject(error);
749
+ });
750
+ res.on("close", () => {
751
+ abort();
752
+ resolve();
753
+ });
754
+ }, "executeStream");
755
+ executeStream().catch((error) => {
756
+ const componentName = typeof viewComponent === "function" ? viewComponent.name : String(viewComponent);
757
+ this.streamingErrorHandler.handleShellError(error, res, componentName, context.isDevelopment);
758
+ resolve();
759
+ });
760
+ });
761
+ }
762
+ };
763
+ StreamRenderer = _ts_decorate4([
764
+ Injectable(),
765
+ _ts_metadata3("design:type", Function),
766
+ _ts_metadata3("design:paramtypes", [
767
+ typeof TemplateParserService === "undefined" ? Object : TemplateParserService,
768
+ typeof StreamingErrorHandler === "undefined" ? Object : StreamingErrorHandler
769
+ ])
770
+ ], StreamRenderer);
771
+
386
772
  // src/render/render.service.ts
387
- function _ts_decorate3(decorators, target, key, desc) {
773
+ function _ts_decorate5(decorators, target, key, desc) {
388
774
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
389
775
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
390
776
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
391
777
  return c > 3 && r && Object.defineProperty(target, key, r), r;
392
778
  }
393
- __name(_ts_decorate3, "_ts_decorate");
394
- function _ts_metadata2(k, v) {
779
+ __name(_ts_decorate5, "_ts_decorate");
780
+ function _ts_metadata4(k, v) {
395
781
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
396
782
  }
397
- __name(_ts_metadata2, "_ts_metadata");
783
+ __name(_ts_metadata4, "_ts_metadata");
398
784
  function _ts_param2(paramIndex, decorator) {
399
785
  return function(target, key) {
400
786
  decorator(target, key, paramIndex);
@@ -405,8 +791,8 @@ var RenderService = class _RenderService {
405
791
  static {
406
792
  __name(this, "RenderService");
407
793
  }
408
- templateParser;
409
- streamingErrorHandler;
794
+ stringRenderer;
795
+ streamRenderer;
410
796
  defaultHead;
411
797
  logger = new Logger(_RenderService.name);
412
798
  vite = null;
@@ -418,12 +804,12 @@ var RenderService = class _RenderService {
418
804
  entryServerPath;
419
805
  rootLayout = void 0;
420
806
  rootLayoutChecked = false;
421
- constructor(templateParser, streamingErrorHandler, ssrMode, defaultHead, customTemplate) {
422
- this.templateParser = templateParser;
423
- this.streamingErrorHandler = streamingErrorHandler;
807
+ constructor(stringRenderer, streamRenderer, ssrMode, defaultHead, customTemplate) {
808
+ this.stringRenderer = stringRenderer;
809
+ this.streamRenderer = streamRenderer;
424
810
  this.defaultHead = defaultHead;
425
811
  this.isDevelopment = process.env.NODE_ENV !== "production";
426
- this.ssrMode = ssrMode || process.env.SSR_MODE || "stream";
812
+ this.ssrMode = ssrMode || process.env.SSR_MODE || "string";
427
813
  const absoluteServerPath = join(__dirname, "/templates/entry-server.tsx");
428
814
  const relativeServerPath = relative(process.cwd(), absoluteServerPath);
429
815
  if (relativeServerPath.startsWith("..")) {
@@ -431,67 +817,82 @@ var RenderService = class _RenderService {
431
817
  } else {
432
818
  this.entryServerPath = "/" + relativeServerPath.replace(/\\/g, "/");
433
819
  }
820
+ this.template = this.loadTemplate(customTemplate);
821
+ if (!this.isDevelopment) {
822
+ this.loadManifests();
823
+ }
824
+ }
825
+ /**
826
+ * Load HTML template from custom path, package, or local location
827
+ */
828
+ loadTemplate(customTemplate) {
434
829
  if (customTemplate) {
435
- if (customTemplate.includes("<!DOCTYPE") || customTemplate.includes("<html")) {
436
- this.template = customTemplate;
437
- this.logger.log(`\u2713 Loaded custom template (inline)`);
830
+ return this.loadCustomTemplate(customTemplate);
831
+ }
832
+ return this.loadDefaultTemplate();
833
+ }
834
+ loadCustomTemplate(customTemplate) {
835
+ if (customTemplate.includes("<!DOCTYPE") || customTemplate.includes("<html")) {
836
+ this.logger.log(`\u2713 Loaded custom template (inline)`);
837
+ return customTemplate;
838
+ }
839
+ const customTemplatePath = customTemplate.startsWith("/") ? customTemplate : join(process.cwd(), customTemplate);
840
+ if (!existsSync(customTemplatePath)) {
841
+ throw new Error(`Custom template file not found at ${customTemplatePath}`);
842
+ }
843
+ try {
844
+ const template = readFileSync(customTemplatePath, "utf-8");
845
+ this.logger.log(`\u2713 Loaded custom template from ${customTemplatePath}`);
846
+ return template;
847
+ } catch (error) {
848
+ throw new Error(`Failed to read custom template file at ${customTemplatePath}: ${error.message}`);
849
+ }
850
+ }
851
+ loadDefaultTemplate() {
852
+ let templatePath;
853
+ if (this.isDevelopment) {
854
+ const packageTemplatePaths = [
855
+ join(__dirname, "../templates/index.html"),
856
+ join(__dirname, "../src/templates/index.html"),
857
+ join(__dirname, "../../src/templates/index.html")
858
+ ];
859
+ const localTemplatePath = join(process.cwd(), "src/views/index.html");
860
+ const foundPackageTemplate = packageTemplatePaths.find((p) => existsSync(p));
861
+ if (foundPackageTemplate) {
862
+ templatePath = foundPackageTemplate;
863
+ } else if (existsSync(localTemplatePath)) {
864
+ templatePath = localTemplatePath;
438
865
  } else {
439
- const customTemplatePath = customTemplate.startsWith("/") ? customTemplate : join(process.cwd(), customTemplate);
440
- if (!existsSync(customTemplatePath)) {
441
- throw new Error(`Custom template file not found at ${customTemplatePath}`);
442
- }
443
- try {
444
- this.template = readFileSync(customTemplatePath, "utf-8");
445
- this.logger.log(`\u2713 Loaded custom template from ${customTemplatePath}`);
446
- } catch (error) {
447
- throw new Error(`Failed to read custom template file at ${customTemplatePath}: ${error.message}`);
448
- }
449
- }
450
- } else {
451
- let templatePath;
452
- if (this.isDevelopment) {
453
- const packageTemplatePaths = [
454
- join(__dirname, "../templates/index.html"),
455
- join(__dirname, "../src/templates/index.html"),
456
- join(__dirname, "../../src/templates/index.html")
457
- ];
458
- const localTemplatePath = join(process.cwd(), "src/views/index.html");
459
- const foundPackageTemplate = packageTemplatePaths.find((p) => existsSync(p));
460
- if (foundPackageTemplate) {
461
- templatePath = foundPackageTemplate;
462
- } else if (existsSync(localTemplatePath)) {
463
- templatePath = localTemplatePath;
464
- } else {
465
- throw new Error(`Template file not found. Tried:
866
+ throw new Error(`Template file not found. Tried:
466
867
  ` + packageTemplatePaths.map((p) => ` - ${p} (package template)`).join("\n") + `
467
868
  - ${localTemplatePath} (local template)`);
468
- }
469
- } else {
470
- templatePath = join(process.cwd(), "dist/client/index.html");
471
- if (!existsSync(templatePath)) {
472
- throw new Error(`Template file not found at ${templatePath}. Make sure to run the build process first.`);
473
- }
474
869
  }
475
- try {
476
- this.template = readFileSync(templatePath, "utf-8");
477
- this.logger.log(`\u2713 Loaded template from ${templatePath}`);
478
- } catch (error) {
479
- throw new Error(`Failed to read template file at ${templatePath}: ${error.message}`);
870
+ } else {
871
+ templatePath = join(process.cwd(), "dist/client/index.html");
872
+ if (!existsSync(templatePath)) {
873
+ throw new Error(`Template file not found at ${templatePath}. Make sure to run the build process first.`);
480
874
  }
481
875
  }
482
- if (!this.isDevelopment) {
483
- const manifestPath = join(process.cwd(), "dist/client/.vite/manifest.json");
484
- if (existsSync(manifestPath)) {
485
- this.manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
486
- } else {
487
- this.logger.warn("\u26A0\uFE0F Client manifest not found. Run `pnpm build:client` first.");
488
- }
489
- const serverManifestPath = join(process.cwd(), "dist/server/.vite/manifest.json");
490
- if (existsSync(serverManifestPath)) {
491
- this.serverManifest = JSON.parse(readFileSync(serverManifestPath, "utf-8"));
492
- } else {
493
- this.logger.warn("\u26A0\uFE0F Server manifest not found. Run `pnpm build:server` first.");
494
- }
876
+ try {
877
+ const template = readFileSync(templatePath, "utf-8");
878
+ this.logger.log(`\u2713 Loaded template from ${templatePath}`);
879
+ return template;
880
+ } catch (error) {
881
+ throw new Error(`Failed to read template file at ${templatePath}: ${error.message}`);
882
+ }
883
+ }
884
+ loadManifests() {
885
+ const manifestPath = join(process.cwd(), "dist/client/.vite/manifest.json");
886
+ if (existsSync(manifestPath)) {
887
+ this.manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
888
+ } else {
889
+ this.logger.warn("\u26A0\uFE0F Client manifest not found. Run `pnpm build:client` first.");
890
+ }
891
+ const serverManifestPath = join(process.cwd(), "dist/server/.vite/manifest.json");
892
+ if (existsSync(serverManifestPath)) {
893
+ this.serverManifest = JSON.parse(readFileSync(serverManifestPath, "utf-8"));
894
+ } else {
895
+ this.logger.warn("\u26A0\uFE0F Server manifest not found. Run `pnpm build:server` first.");
495
896
  }
496
897
  }
497
898
  setViteServer(vite) {
@@ -545,16 +946,50 @@ var RenderService = class _RenderService {
545
946
  }
546
947
  /**
547
948
  * Main render method that routes to string or stream mode
949
+ *
950
+ * String mode (default):
951
+ * - Returns complete HTML string
952
+ * - Atomic responses - works completely or fails completely
953
+ * - Proper HTTP status codes always
954
+ *
955
+ * Stream mode:
956
+ * - Writes directly to response
957
+ * - Better TTFB, progressive rendering
958
+ * - Requires response object
548
959
  */
549
960
  async render(viewComponent, data = {}, res, head) {
550
961
  const mergedHead = this.mergeHead(this.defaultHead, head);
962
+ const renderContext = {
963
+ template: this.template,
964
+ vite: this.vite,
965
+ manifest: this.manifest,
966
+ serverManifest: this.serverManifest,
967
+ entryServerPath: this.entryServerPath,
968
+ isDevelopment: this.isDevelopment
969
+ };
551
970
  if (this.ssrMode === "stream") {
552
971
  if (!res) {
553
972
  throw new Error("Response object is required for streaming SSR mode. Pass res as third parameter.");
554
973
  }
555
- return this.renderToStream(viewComponent, data, res, mergedHead);
974
+ return this.streamRenderer.render(viewComponent, data, res, renderContext, mergedHead);
556
975
  }
557
- return this.renderToString(viewComponent, data, mergedHead);
976
+ return this.stringRenderer.render(viewComponent, data, renderContext, mergedHead);
977
+ }
978
+ /**
979
+ * Render a segment for client-side navigation.
980
+ * Always uses string mode (streaming not supported for segments).
981
+ */
982
+ async renderSegment(viewComponent, data, swapTarget, head) {
983
+ const mergedHead = this.mergeHead(this.defaultHead, head);
984
+ const renderContext = {
985
+ template: this.template,
986
+ vite: this.vite,
987
+ manifest: this.manifest,
988
+ serverManifest: this.serverManifest,
989
+ entryServerPath: this.entryServerPath,
990
+ isDevelopment: this.isDevelopment
991
+ };
992
+ return this.stringRenderer.renderSegment(viewComponent, data, renderContext, swapTarget, mergedHead);
558
993
  }
559
994
  /**
560
995
  * Merge default head with page-specific head
@@ -567,7 +1002,6 @@ var RenderService = class _RenderService {
567
1002
  return {
568
1003
  ...defaultHead,
569
1004
  ...pageHead,
570
- // Merge arrays (links and meta) instead of replacing
571
1005
  links: [
572
1006
  ...defaultHead?.links || [],
573
1007
  ...pageHead?.links || []
@@ -578,198 +1012,8 @@ var RenderService = class _RenderService {
578
1012
  ]
579
1013
  };
580
1014
  }
581
- /**
582
- * Traditional string-based SSR using renderToString
583
- */
584
- async renderToString(viewComponent, data = {}, head) {
585
- const startTime = Date.now();
586
- try {
587
- let template = this.template;
588
- if (this.vite) {
589
- template = await this.vite.transformIndexHtml("/", template);
590
- }
591
- let renderModule;
592
- if (this.vite) {
593
- renderModule = await this.vite.ssrLoadModule(this.entryServerPath);
594
- } else {
595
- if (this.serverManifest) {
596
- const manifestEntry = Object.entries(this.serverManifest).find(([key, value]) => value.isEntry && key.includes("entry-server"));
597
- if (manifestEntry) {
598
- const [, entry] = manifestEntry;
599
- const serverPath = join(process.cwd(), "dist/server", entry.file);
600
- renderModule = await import(serverPath);
601
- } else {
602
- throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
603
- }
604
- } else {
605
- throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
606
- }
607
- }
608
- const { data: pageData, __context: context, __layouts: layouts } = data;
609
- const appHtml = await renderModule.renderComponent(viewComponent, data);
610
- const componentName = viewComponent.displayName || viewComponent.name || "Component";
611
- const layoutMetadata = layouts ? layouts.map((l) => ({
612
- name: l.layout.displayName || l.layout.name || "default",
613
- props: l.props
614
- })) : [];
615
- const initialStateScript = `
616
- <script>
617
- window.__INITIAL_STATE__ = ${uneval(pageData)};
618
- window.__CONTEXT__ = ${uneval(context)};
619
- window.__COMPONENT_NAME__ = ${uneval(componentName)};
620
- window.__LAYOUTS__ = ${uneval(layoutMetadata)};
621
- </script>
622
- `;
623
- let clientScript = "";
624
- let styles = "";
625
- if (this.vite) {
626
- clientScript = `<script type="module" src="/src/views/entry-client.tsx"></script>`;
627
- styles = "";
628
- } else {
629
- if (this.manifest) {
630
- const manifestEntry = Object.entries(this.manifest).find(([key, value]) => value.isEntry && key.includes("entry-client"));
631
- if (manifestEntry) {
632
- const [, entry] = manifestEntry;
633
- const entryFile = entry.file;
634
- clientScript = `<script type="module" src="/${entryFile}"></script>`;
635
- if (entry.css) {
636
- const cssFiles = entry.css;
637
- styles = cssFiles.map((css) => `<link rel="stylesheet" href="/${css}" />`).join("\n ");
638
- }
639
- } else {
640
- this.logger.error("\u26A0\uFE0F Client entry not found in manifest");
641
- clientScript = `<script type="module" src="/assets/client.js"></script>`;
642
- }
643
- } else {
644
- this.logger.error("\u26A0\uFE0F Client manifest not found");
645
- clientScript = `<script type="module" src="/assets/client.js"></script>`;
646
- }
647
- }
648
- const headTags = this.templateParser.buildHeadTags(head);
649
- let html = template.replace("<!--app-html-->", appHtml);
650
- html = html.replace("<!--initial-state-->", initialStateScript);
651
- html = html.replace("<!--client-scripts-->", clientScript);
652
- html = html.replace("<!--styles-->", styles);
653
- html = html.replace("<!--head-meta-->", headTags);
654
- if (this.isDevelopment) {
655
- const duration = Date.now() - startTime;
656
- const componentName2 = typeof viewComponent === "function" ? viewComponent.name : String(viewComponent);
657
- this.logger.log(`[SSR] ${componentName2} rendered in ${duration}ms (string mode)`);
658
- }
659
- return html;
660
- } catch (error) {
661
- throw error;
662
- }
663
- }
664
- /**
665
- * Modern streaming SSR using renderToPipeableStream
666
- */
667
- async renderToStream(viewComponent, data = {}, res, head) {
668
- const startTime = Date.now();
669
- let shellReadyTime = 0;
670
- return new Promise((resolve, reject) => {
671
- const executeStream = /* @__PURE__ */ __name(async () => {
672
- let template = this.template;
673
- if (this.vite) {
674
- template = await this.vite.transformIndexHtml("/", template);
675
- }
676
- const templateParts = this.templateParser.parseTemplate(template);
677
- let renderModule;
678
- if (this.vite) {
679
- renderModule = await this.vite.ssrLoadModule(this.entryServerPath);
680
- } else {
681
- if (this.serverManifest) {
682
- const manifestEntry = Object.entries(this.serverManifest).find(([key, value]) => value.isEntry && key.includes("entry-server"));
683
- if (manifestEntry) {
684
- const [, entry] = manifestEntry;
685
- const serverPath = join(process.cwd(), "dist/server", entry.file);
686
- renderModule = await import(serverPath);
687
- } else {
688
- throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
689
- }
690
- } else {
691
- throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
692
- }
693
- }
694
- const { data: pageData, __context: context, __layouts: layouts } = data;
695
- const componentName = viewComponent.displayName || viewComponent.name || "Component";
696
- const inlineScripts = this.templateParser.buildInlineScripts(pageData, context, componentName, layouts);
697
- const clientScript = this.templateParser.getClientScriptTag(this.isDevelopment, this.manifest);
698
- const stylesheetTags = this.templateParser.getStylesheetTags(this.isDevelopment, this.manifest);
699
- const headTags = this.templateParser.buildHeadTags(head);
700
- let didError = false;
701
- let shellErrorOccurred = false;
702
- const { PassThrough } = await import('stream');
703
- const reactStream = new PassThrough();
704
- let allReadyFired = false;
705
- const { pipe, abort } = renderModule.renderComponentStream(viewComponent, data, {
706
- onShellReady: /* @__PURE__ */ __name(() => {
707
- shellReadyTime = Date.now();
708
- if (!res.headersSent) {
709
- res.statusCode = didError ? 500 : 200;
710
- res.setHeader("Content-Type", "text/html; charset=utf-8");
711
- }
712
- let htmlStart = templateParts.htmlStart;
713
- htmlStart = htmlStart.replace("<!--styles-->", stylesheetTags);
714
- htmlStart = htmlStart.replace("<!--head-meta-->", headTags);
715
- res.write(htmlStart);
716
- res.write(templateParts.rootStart);
717
- pipe(reactStream);
718
- reactStream.pipe(res, {
719
- end: false
720
- });
721
- if (this.isDevelopment) {
722
- const ttfb = shellReadyTime - startTime;
723
- this.logger.log(`[SSR] ${componentName} shell ready in ${ttfb}ms (stream mode - TTFB)`);
724
- }
725
- }, "onShellReady"),
726
- onShellError: /* @__PURE__ */ __name((error) => {
727
- shellErrorOccurred = true;
728
- this.streamingErrorHandler.handleShellError(error, res, componentName, this.isDevelopment);
729
- resolve();
730
- }, "onShellError"),
731
- onError: /* @__PURE__ */ __name((error) => {
732
- didError = true;
733
- this.streamingErrorHandler.handleStreamError(error, componentName);
734
- }, "onError"),
735
- onAllReady: /* @__PURE__ */ __name(() => {
736
- allReadyFired = true;
737
- }, "onAllReady")
738
- });
739
- reactStream.on("end", () => {
740
- if (shellErrorOccurred) {
741
- return;
742
- }
743
- res.write(inlineScripts);
744
- res.write(clientScript);
745
- res.write(templateParts.rootEnd);
746
- res.write(templateParts.htmlEnd);
747
- res.end();
748
- if (this.isDevelopment) {
749
- const totalTime = Date.now() - startTime;
750
- const streamTime = Date.now() - shellReadyTime;
751
- const viaAllReady = allReadyFired ? " (onAllReady fired)" : " (onAllReady never fired)";
752
- this.logger.log(`[SSR] ${componentName} streaming complete in ${totalTime}ms total (${streamTime}ms streaming)${viaAllReady}`);
753
- }
754
- resolve();
755
- });
756
- reactStream.on("error", (error) => {
757
- reject(error);
758
- });
759
- res.on("close", () => {
760
- abort();
761
- resolve();
762
- });
763
- }, "executeStream");
764
- executeStream().catch((error) => {
765
- const componentName = typeof viewComponent === "function" ? viewComponent.name : String(viewComponent);
766
- this.streamingErrorHandler.handleShellError(error, res, componentName, this.isDevelopment);
767
- resolve();
768
- });
769
- });
770
- }
771
1015
  };
772
- RenderService = _ts_decorate3([
1016
+ RenderService = _ts_decorate5([
773
1017
  Injectable(),
774
1018
  _ts_param2(2, Optional()),
775
1019
  _ts_param2(2, Inject("SSR_MODE")),
@@ -777,10 +1021,10 @@ RenderService = _ts_decorate3([
777
1021
  _ts_param2(3, Inject("DEFAULT_HEAD")),
778
1022
  _ts_param2(4, Optional()),
779
1023
  _ts_param2(4, Inject("CUSTOM_TEMPLATE")),
780
- _ts_metadata2("design:type", Function),
781
- _ts_metadata2("design:paramtypes", [
782
- typeof TemplateParserService === "undefined" ? Object : TemplateParserService,
783
- typeof StreamingErrorHandler === "undefined" ? Object : StreamingErrorHandler,
1024
+ _ts_metadata4("design:type", Function),
1025
+ _ts_metadata4("design:paramtypes", [
1026
+ typeof StringRenderer === "undefined" ? Object : StringRenderer,
1027
+ typeof StreamRenderer === "undefined" ? Object : StreamRenderer,
784
1028
  typeof SSRMode === "undefined" ? Object : SSRMode,
785
1029
  typeof HeadData === "undefined" ? Object : HeadData,
786
1030
  String
@@ -809,17 +1053,17 @@ function Layout(layout, options) {
809
1053
  __name(Layout, "Layout");
810
1054
 
811
1055
  // src/render/render.interceptor.ts
812
- function _ts_decorate4(decorators, target, key, desc) {
1056
+ function _ts_decorate6(decorators, target, key, desc) {
813
1057
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
814
1058
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
815
1059
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
816
1060
  return c > 3 && r && Object.defineProperty(target, key, r), r;
817
1061
  }
818
- __name(_ts_decorate4, "_ts_decorate");
819
- function _ts_metadata3(k, v) {
1062
+ __name(_ts_decorate6, "_ts_decorate");
1063
+ function _ts_metadata5(k, v) {
820
1064
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
821
1065
  }
822
- __name(_ts_metadata3, "_ts_metadata");
1066
+ __name(_ts_metadata5, "_ts_metadata");
823
1067
  function _ts_param3(paramIndex, decorator) {
824
1068
  return function(target, key) {
825
1069
  decorator(target, key, paramIndex);
@@ -869,15 +1113,20 @@ var RenderInterceptor = class {
869
1113
  } else if (renderOptions?.layout === false) {
870
1114
  return layouts;
871
1115
  }
872
- if (controllerLayoutMeta) {
873
- const mergedProps = {
874
- ...controllerLayoutMeta.options?.props || {},
875
- ...dynamicLayoutProps || {}
876
- };
877
- layouts.push({
878
- layout: controllerLayoutMeta.layout,
879
- props: mergedProps
880
- });
1116
+ if (controllerLayoutMeta?.layout) {
1117
+ const rootLayoutName = rootLayout?.displayName || rootLayout?.name;
1118
+ const controllerLayoutName = controllerLayoutMeta.layout.displayName || controllerLayoutMeta.layout.name;
1119
+ const isDuplicateOfRoot = rootLayout && rootLayoutName && controllerLayoutName === rootLayoutName;
1120
+ if (!isDuplicateOfRoot) {
1121
+ const mergedProps = {
1122
+ ...controllerLayoutMeta.options?.props || {},
1123
+ ...dynamicLayoutProps || {}
1124
+ };
1125
+ layouts.push({
1126
+ layout: controllerLayoutMeta.layout,
1127
+ props: mergedProps
1128
+ });
1129
+ }
881
1130
  }
882
1131
  if (renderOptions?.layout) {
883
1132
  const mergedProps = {
@@ -891,6 +1140,53 @@ var RenderInterceptor = class {
891
1140
  }
892
1141
  return layouts;
893
1142
  }
1143
+ /**
1144
+ * Detect request type based on headers.
1145
+ * - If X-Current-Layouts header is present, this is a segment request
1146
+ * - Only GET requests can be segments
1147
+ */
1148
+ detectRequestType(request) {
1149
+ if (request.method !== "GET") {
1150
+ return {
1151
+ type: "full"
1152
+ };
1153
+ }
1154
+ const layoutsHeader = request.headers["x-current-layouts"];
1155
+ if (layoutsHeader && typeof layoutsHeader === "string") {
1156
+ const currentLayouts = layoutsHeader.split(",").map((s) => s.trim());
1157
+ return {
1158
+ type: "segment",
1159
+ currentLayouts
1160
+ };
1161
+ }
1162
+ return {
1163
+ type: "full"
1164
+ };
1165
+ }
1166
+ /**
1167
+ * Determine swap target by finding deepest common layout.
1168
+ * Returns null if no common ancestor (client should do full navigation).
1169
+ */
1170
+ determineSwapTarget(currentLayouts, targetLayouts) {
1171
+ const targetNames = targetLayouts.map((l) => l.layout.displayName || l.layout.name);
1172
+ let commonLayout = null;
1173
+ for (let i = 0; i < Math.min(currentLayouts.length, targetNames.length); i++) {
1174
+ if (currentLayouts[i] === targetNames[i]) {
1175
+ commonLayout = currentLayouts[i];
1176
+ } else {
1177
+ break;
1178
+ }
1179
+ }
1180
+ return commonLayout;
1181
+ }
1182
+ /**
1183
+ * Filter layouts to only include those below the swap target.
1184
+ * The swap target's outlet will contain the filtered layouts.
1185
+ */
1186
+ filterLayoutsFromSwapTarget(layouts, swapTarget) {
1187
+ const index = layouts.findIndex((l) => (l.layout.displayName || l.layout.name) === swapTarget);
1188
+ return index >= 0 ? layouts.slice(index + 1) : layouts;
1189
+ }
894
1190
  intercept(context, next) {
895
1191
  const viewPathOrComponent = this.reflector.get(RENDER_KEY, context.getHandler());
896
1192
  if (!viewPathOrComponent) {
@@ -939,6 +1235,24 @@ var RenderInterceptor = class {
939
1235
  __context: renderContext,
940
1236
  __layouts: layoutChain
941
1237
  };
1238
+ const { type, currentLayouts } = this.detectRequestType(request);
1239
+ if (type === "segment" && currentLayouts) {
1240
+ const swapTarget = this.determineSwapTarget(currentLayouts, layoutChain);
1241
+ if (!swapTarget) {
1242
+ response.type("application/json");
1243
+ return {
1244
+ swapTarget: null
1245
+ };
1246
+ }
1247
+ const filteredLayouts = this.filterLayoutsFromSwapTarget(layoutChain, swapTarget);
1248
+ const segmentData = {
1249
+ ...fullData,
1250
+ __layouts: filteredLayouts
1251
+ };
1252
+ const result = await this.renderService.renderSegment(viewPathOrComponent, segmentData, swapTarget, renderResponse.head);
1253
+ response.type("application/json");
1254
+ return result;
1255
+ }
942
1256
  try {
943
1257
  const html = await this.renderService.render(viewPathOrComponent, fullData, response, renderResponse.head);
944
1258
  if (html !== void 0) {
@@ -952,31 +1266,31 @@ var RenderInterceptor = class {
952
1266
  }));
953
1267
  }
954
1268
  };
955
- RenderInterceptor = _ts_decorate4([
1269
+ RenderInterceptor = _ts_decorate6([
956
1270
  Injectable(),
957
1271
  _ts_param3(2, Optional()),
958
1272
  _ts_param3(2, Inject("ALLOWED_HEADERS")),
959
1273
  _ts_param3(3, Optional()),
960
1274
  _ts_param3(3, Inject("ALLOWED_COOKIES")),
961
- _ts_metadata3("design:type", Function),
962
- _ts_metadata3("design:paramtypes", [
1275
+ _ts_metadata5("design:type", Function),
1276
+ _ts_metadata5("design:paramtypes", [
963
1277
  typeof Reflector === "undefined" ? Object : Reflector,
964
1278
  typeof RenderService === "undefined" ? Object : RenderService,
965
1279
  Array,
966
1280
  Array
967
1281
  ])
968
1282
  ], RenderInterceptor);
969
- function _ts_decorate5(decorators, target, key, desc) {
1283
+ function _ts_decorate7(decorators, target, key, desc) {
970
1284
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
971
1285
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
972
1286
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
973
1287
  return c > 3 && r && Object.defineProperty(target, key, r), r;
974
1288
  }
975
- __name(_ts_decorate5, "_ts_decorate");
976
- function _ts_metadata4(k, v) {
1289
+ __name(_ts_decorate7, "_ts_decorate");
1290
+ function _ts_metadata6(k, v) {
977
1291
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
978
1292
  }
979
- __name(_ts_metadata4, "_ts_metadata");
1293
+ __name(_ts_metadata6, "_ts_metadata");
980
1294
  function _ts_param4(paramIndex, decorator) {
981
1295
  return function(target, key) {
982
1296
  decorator(target, key, paramIndex);
@@ -990,14 +1304,12 @@ var ViteInitializerService = class _ViteInitializerService {
990
1304
  renderService;
991
1305
  httpAdapterHost;
992
1306
  logger = new Logger(_ViteInitializerService.name);
993
- viteMode;
994
1307
  vitePort;
995
1308
  viteServer = null;
996
1309
  isShuttingDown = false;
997
1310
  constructor(renderService, httpAdapterHost, viteConfig) {
998
1311
  this.renderService = renderService;
999
1312
  this.httpAdapterHost = httpAdapterHost;
1000
- this.viteMode = viteConfig?.mode || "embedded";
1001
1313
  this.vitePort = viteConfig?.port || 5173;
1002
1314
  this.registerSignalHandlers();
1003
1315
  }
@@ -1029,30 +1341,12 @@ var ViteInitializerService = class _ViteInitializerService {
1029
1341
  appType: "custom"
1030
1342
  });
1031
1343
  this.renderService.setViteServer(this.viteServer);
1032
- if (this.viteMode === "embedded") {
1033
- await this.mountViteMiddleware(this.viteServer);
1034
- } else if (this.viteMode === "proxy") {
1035
- await this.setupViteProxy();
1036
- }
1037
- this.logger.log(`\u2713 Vite initialized for SSR (mode: ${this.viteMode})`);
1344
+ await this.setupViteProxy();
1345
+ this.logger.log("\u2713 Vite initialized for SSR");
1038
1346
  } catch (error) {
1039
1347
  this.logger.warn(`Failed to initialize Vite: ${error.message}. Make sure vite is installed.`);
1040
1348
  }
1041
1349
  }
1042
- async mountViteMiddleware(vite) {
1043
- try {
1044
- const httpAdapter = this.httpAdapterHost.httpAdapter;
1045
- if (!httpAdapter) {
1046
- this.logger.warn("HTTP adapter not available, skipping Vite middleware setup");
1047
- return;
1048
- }
1049
- const app = httpAdapter.getInstance();
1050
- app.use(vite.middlewares);
1051
- this.logger.log(`\u2713 Vite middleware mounted (embedded mode with HMR)`);
1052
- } catch (error) {
1053
- this.logger.warn(`Failed to mount Vite middleware: ${error.message}`);
1054
- }
1055
- }
1056
1350
  async setupViteProxy() {
1057
1351
  try {
1058
1352
  const httpAdapter = this.httpAdapterHost.httpAdapter;
@@ -1071,7 +1365,7 @@ var ViteInitializerService = class _ViteInitializerService {
1071
1365
  }, "pathFilter")
1072
1366
  });
1073
1367
  app.use(viteProxy);
1074
- this.logger.log(`\u2713 Vite HMR proxy configured (external Vite on port ${this.vitePort})`);
1368
+ this.logger.log(`\u2713 Vite HMR proxy configured (Vite dev server on port ${this.vitePort})`);
1075
1369
  } catch (error) {
1076
1370
  this.logger.warn(`Failed to setup Vite proxy: ${error.message}. Make sure http-proxy-middleware is installed.`);
1077
1371
  }
@@ -1122,12 +1416,12 @@ var ViteInitializerService = class _ViteInitializerService {
1122
1416
  }
1123
1417
  }
1124
1418
  };
1125
- ViteInitializerService = _ts_decorate5([
1419
+ ViteInitializerService = _ts_decorate7([
1126
1420
  Injectable(),
1127
1421
  _ts_param4(2, Optional()),
1128
1422
  _ts_param4(2, Inject("VITE_CONFIG")),
1129
- _ts_metadata4("design:type", Function),
1130
- _ts_metadata4("design:paramtypes", [
1423
+ _ts_metadata6("design:type", Function),
1424
+ _ts_metadata6("design:paramtypes", [
1131
1425
  typeof RenderService === "undefined" ? Object : RenderService,
1132
1426
  typeof HttpAdapterHost === "undefined" ? Object : HttpAdapterHost,
1133
1427
  typeof ViteConfig === "undefined" ? Object : ViteConfig
@@ -1135,13 +1429,13 @@ ViteInitializerService = _ts_decorate5([
1135
1429
  ], ViteInitializerService);
1136
1430
 
1137
1431
  // src/render/render.module.ts
1138
- function _ts_decorate6(decorators, target, key, desc) {
1432
+ function _ts_decorate8(decorators, target, key, desc) {
1139
1433
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1140
1434
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1141
1435
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1142
1436
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1143
1437
  }
1144
- __name(_ts_decorate6, "_ts_decorate");
1438
+ __name(_ts_decorate8, "_ts_decorate");
1145
1439
  var RenderModule = class _RenderModule {
1146
1440
  static {
1147
1441
  __name(this, "RenderModule");
@@ -1154,17 +1448,17 @@ var RenderModule = class _RenderModule {
1154
1448
  *
1155
1449
  * @example
1156
1450
  * ```ts
1157
- * // Zero config - uses defaults
1451
+ * // Zero config - uses string mode (default, recommended)
1158
1452
  * RenderModule.forRoot()
1159
1453
  *
1160
- * // Enable streaming SSR
1161
- * RenderModule.forRoot({ mode: 'stream' })
1162
- *
1163
- * // Enable HMR with proxy mode
1454
+ * // Custom Vite port
1164
1455
  * RenderModule.forRoot({
1165
- * vite: { mode: 'proxy', port: 5173 }
1456
+ * vite: { port: 3001 }
1166
1457
  * })
1167
1458
  *
1459
+ * // Enable streaming SSR (advanced - see mode docs for trade-offs)
1460
+ * RenderModule.forRoot({ mode: 'stream' })
1461
+ *
1168
1462
  * // Custom error pages
1169
1463
  * RenderModule.forRoot({
1170
1464
  * errorPageDevelopment: DevErrorPage,
@@ -1178,6 +1472,8 @@ var RenderModule = class _RenderModule {
1178
1472
  TemplateParserService,
1179
1473
  StreamingErrorHandler,
1180
1474
  ViteInitializerService,
1475
+ StringRenderer,
1476
+ StreamRenderer,
1181
1477
  {
1182
1478
  provide: APP_INTERCEPTOR,
1183
1479
  useClass: RenderInterceptor
@@ -1282,6 +1578,8 @@ var RenderModule = class _RenderModule {
1282
1578
  TemplateParserService,
1283
1579
  StreamingErrorHandler,
1284
1580
  ViteInitializerService,
1581
+ StringRenderer,
1582
+ StreamRenderer,
1285
1583
  {
1286
1584
  provide: APP_INTERCEPTOR,
1287
1585
  useClass: RenderInterceptor
@@ -1366,7 +1664,7 @@ var RenderModule = class _RenderModule {
1366
1664
  return this.forRootAsync(options);
1367
1665
  }
1368
1666
  };
1369
- RenderModule = _ts_decorate6([
1667
+ RenderModule = _ts_decorate8([
1370
1668
  Global(),
1371
1669
  Module({
1372
1670
  providers: [
@@ -1374,6 +1672,8 @@ RenderModule = _ts_decorate6([
1374
1672
  TemplateParserService,
1375
1673
  StreamingErrorHandler,
1376
1674
  ViteInitializerService,
1675
+ StringRenderer,
1676
+ StreamRenderer,
1377
1677
  {
1378
1678
  provide: APP_INTERCEPTOR,
1379
1679
  useClass: RenderInterceptor
@@ -1388,8 +1688,25 @@ RenderModule = _ts_decorate6([
1388
1688
  ]
1389
1689
  })
1390
1690
  ], RenderModule);
1391
- var PageContext = /* @__PURE__ */ createContext(null);
1392
- function PageContextProvider({ context, children }) {
1691
+ var CONTEXT_KEY = /* @__PURE__ */ Symbol.for("nestjs-ssr.PageContext");
1692
+ var globalStore = globalThis;
1693
+ function getOrCreateContext() {
1694
+ if (!globalStore[CONTEXT_KEY]) {
1695
+ globalStore[CONTEXT_KEY] = /* @__PURE__ */ createContext(null);
1696
+ }
1697
+ return globalStore[CONTEXT_KEY];
1698
+ }
1699
+ __name(getOrCreateContext, "getOrCreateContext");
1700
+ var PageContext = getOrCreateContext();
1701
+ function registerPageContextState(setter) {
1702
+ }
1703
+ __name(registerPageContextState, "registerPageContextState");
1704
+ function PageContextProvider({ context: initialContext, children, isSegment = false }) {
1705
+ const [context, setContext] = useState(initialContext);
1706
+ useEffect(() => {
1707
+ }, [
1708
+ isSegment
1709
+ ]);
1393
1710
  return /* @__PURE__ */ React.createElement(PageContext.Provider, {
1394
1711
  value: context
1395
1712
  }, children);
@@ -1582,5 +1899,14 @@ function createSSRHooks() {
1582
1899
  };
1583
1900
  }
1584
1901
  __name(createSSRHooks, "createSSRHooks");
1902
+ var defaultHooks = createSSRHooks();
1903
+ var usePageContext = defaultHooks.usePageContext;
1904
+ var useParams = defaultHooks.useParams;
1905
+ var useQuery = defaultHooks.useQuery;
1906
+ var useRequest = defaultHooks.useRequest;
1907
+ var useHeaders = defaultHooks.useHeaders;
1908
+ var useHeader = defaultHooks.useHeader;
1909
+ var useCookies = defaultHooks.useCookies;
1910
+ var useCookie = defaultHooks.useCookie;
1585
1911
 
1586
- export { ErrorPageDevelopment, ErrorPageProduction, Layout, PageContextProvider, Render, RenderInterceptor, RenderModule, RenderService, StreamingErrorHandler, TemplateParserService, createSSRHooks };
1912
+ export { ErrorPageDevelopment, ErrorPageProduction, Layout, PageContextProvider, Render, RenderInterceptor, RenderModule, RenderService, StreamingErrorHandler, TemplateParserService, createSSRHooks, useCookie, useCookies, useHeader, useHeaders, usePageContext, useParams, useQuery, useRequest };