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