@nestjs-ssr/react 0.3.6 → 0.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,70 +3,82 @@
3
3
  [![npm version](https://img.shields.io/npm/v/@nestjs-ssr/react)](https://www.npmjs.com/package/@nestjs-ssr/react)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
 
6
- React as a view layer for NestJS. Controllers return data. Components render it. One app.
6
+ **React SSR for NestJS. One app. One deploy. Full type safety from database to DOM.**
7
7
 
8
- **[Documentation](https://georgialexandrov.github.io/nestjs-ssr/)** | **[Getting Started](https://georgialexandrov.github.io/nestjs-ssr/guide/installation)**
8
+ No separate frontend. No second router. No type boundary you maintain by hand. Controllers return data, components render it, TypeScript enforces the contract.
9
9
 
10
- ## Quick Start
10
+ **[Documentation](https://georgialexandrov.github.io/nestjs-ssr/)** | **[Get Started](https://georgialexandrov.github.io/nestjs-ssr/guide/installation)**
11
11
 
12
- ```bash
13
- npx @nestjs-ssr/react init
14
- ```
12
+ ## The whole contract in two files
15
13
 
16
14
  ```typescript
17
- @Get(':id')
18
- @Render(ProductDetail)
19
- async getProduct(@Param('id') id: string) {
20
- return { product: await this.productService.findById(id) };
15
+ // recipes.controller.ts
16
+ import { Controller, Get, Param } from '@nestjs/common';
17
+ import { Render, Layout } from '@nestjs-ssr/react';
18
+ import { RecipesLayout } from './views/recipes-layout';
19
+ import { RecipeDetail } from './views/recipe-detail';
20
+
21
+ @Controller('recipes')
22
+ @Layout(RecipesLayout)
23
+ export class RecipesController {
24
+ @Get(':slug')
25
+ @Render(RecipeDetail)
26
+ getRecipe(@Param('slug') slug: string) {
27
+ const recipe = this.recipes.findBySlug(slug);
28
+ return {
29
+ recipe,
30
+ chef: this.chefs.findById(recipe.chefId),
31
+ head: { title: recipe.name },
32
+ };
33
+ }
21
34
  }
22
35
  ```
23
36
 
24
37
  ```tsx
25
- export default function ProductDetail({
26
- product,
27
- }: PageProps<{ product: Product }>) {
28
- return <h1>{product.name}</h1>;
38
+ // recipe-detail.tsx
39
+ import { PageProps } from '@nestjs-ssr/react';
40
+
41
+ export default function RecipeDetail({
42
+ recipe,
43
+ chef,
44
+ }: PageProps<RecipeDetailProps>) {
45
+ return (
46
+ <article>
47
+ <h1>{recipe.name}</h1>
48
+ <p>{recipe.description}</p>
49
+ <IngredientList items={recipe.ingredients} />
50
+ <ChefCard chef={chef} />
51
+ </article>
52
+ );
29
53
  }
30
54
  ```
31
55
 
32
- Type mismatch = build fails.
56
+ Return the wrong shape and TypeScript catches it before the code runs.
33
57
 
34
- ## Test in Isolation
58
+ ## Nothing breaks
35
59
 
36
- ```typescript
37
- // Controller: no React
38
- expect(await controller.getProduct('123')).toEqual({ product: { id: '123' } });
39
-
40
- // Component: no NestJS
41
- render(<ProductDetail data={{ product: mockProduct }} />);
42
- ```
60
+ Your NestJS app stays exactly as it is. Routing, guards, pipes, interceptors, services, modules, testing — all unchanged. You're adding a view layer, not rewriting your backend.
43
61
 
44
- ## What You Get
62
+ ## Everything improves
45
63
 
46
- **Rendering:**
64
+ - **One type, both sides** — controller return type is the component's props. Change one, the other breaks at build time.
65
+ - **Layouts that stay put** — nested layouts persist across navigations. Header, sidebar, shell — rendered once, never re-mounted.
66
+ - **No full reloads** — link clicks fetch only the changed segment. State, scroll position, animations stay alive.
67
+ - **Stream or string** — `renderToString` for simplicity, `renderToPipeableStream` for performance. Suspense boundaries stream as they resolve.
68
+ - **SEO out of the box** — title, meta, Open Graph, JSON-LD. Return `head` from your controller.
69
+ - **Vite HMR** — instant updates, no page refresh. Works with Express and Fastify.
47
70
 
48
- - Type-safe data flow from controller to component
49
- - Hierarchical layouts (root → controller → method)
50
- - Head tags (title, meta, OG, JSON-LD)
51
- - Stream or string mode
71
+ ## Add it to an existing NestJS app
52
72
 
53
- **Request Context:**
54
-
55
- - Hooks: `useParams()`, `useQuery()`, `useHeader()`, `useCookie()`
56
- - Whitelist what reaches the client
57
-
58
- **Development:**
73
+ ```bash
74
+ npx @nestjs-ssr/react init
75
+ ```
59
76
 
60
- - Integrated mode: Vite inside NestJS, one process
61
- - Separate mode: standalone Vite server, true HMR
77
+ One command. Works with Express and Fastify.
62
78
 
63
79
  ## Requirements
64
80
 
65
- - Node.js 20+
66
- - NestJS 11+
67
- - React 19+
68
- - Vite 6+
69
- - TypeScript 5+
81
+ Node.js 20+ / NestJS 11+ / React 19+ / Vite 6+ / TypeScript 5+
70
82
 
71
83
  ## Documentation
72
84
 
@@ -74,6 +86,8 @@ render(<ProductDetail data={{ product: mockProduct }} />);
74
86
 
75
87
  - [Installation](https://georgialexandrov.github.io/nestjs-ssr/guide/installation)
76
88
  - [Rendering](https://georgialexandrov.github.io/nestjs-ssr/guide/rendering)
89
+ - [Layouts](https://georgialexandrov.github.io/nestjs-ssr/guide/layouts)
90
+ - [Client-Side Navigation](https://georgialexandrov.github.io/nestjs-ssr/guide/navigation)
77
91
  - [Request Context](https://georgialexandrov.github.io/nestjs-ssr/guide/request-context)
78
92
  - [Configuration](https://georgialexandrov.github.io/nestjs-ssr/guide/configuration)
79
93
  - [API Reference](https://georgialexandrov.github.io/nestjs-ssr/guide/api)
package/dist/client.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- export { a as PageContextProvider, P as PageProps, c as createSSRHooks, j as updatePageContext, i as useCookie, h as useCookies, g as useHeader, f as useHeaders, u as usePageContext, b as useParams, d as useQuery, e as useRequest } from './use-page-context-CVC9DHcL.mjs';
1
+ export { a as PageContextProvider, P as PageProps, c as createSSRHooks, j as updatePageContext, u as useCookie, b as useCookies, d as useHeader, e as useHeaders, f as usePageContext, g as useParams, h as useQuery, i as useRequest } from './use-page-context-CmxWHIK3.mjs';
2
2
  import * as react_jsx_runtime from 'react/jsx-runtime';
3
3
  import React from 'react';
4
4
  export { R as RenderContext } from './render-response.interface-ClWJXKL4.mjs';
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { a as PageContextProvider, P as PageProps, c as createSSRHooks, j as updatePageContext, i as useCookie, h as useCookies, g as useHeader, f as useHeaders, u as usePageContext, b as useParams, d as useQuery, e as useRequest } from './use-page-context-DChgHhL9.js';
1
+ export { a as PageContextProvider, P as PageProps, c as createSSRHooks, j as updatePageContext, u as useCookie, b as useCookies, d as useHeader, e as useHeaders, f as usePageContext, g as useParams, h as useQuery, i as useRequest } from './use-page-context-CUV31oda.js';
2
2
  import * as react_jsx_runtime from 'react/jsx-runtime';
3
3
  import React from 'react';
4
4
  export { R as RenderContext } from './render-response.interface-ClWJXKL4.js';
@@ -759,4 +759,4 @@ declare function ErrorPageDevelopment({ error, viewPath, phase, }: ErrorPageDeve
759
759
  */
760
760
  declare function ErrorPageProduction(): react_jsx_runtime.JSX.Element;
761
761
 
762
- export { type ContextFactory as C, ErrorPageDevelopment as E, RenderModule as R, StreamingErrorHandler as S, TemplateParserService as T, RenderService as a, RenderInterceptor as b, type RenderConfig as c, type SSRMode as d, ErrorPageProduction as e };
762
+ export { type ContextFactory as C, ErrorPageDevelopment as E, type RenderConfig as R, type SSRMode as S, TemplateParserService as T, ErrorPageProduction as a, RenderInterceptor as b, RenderModule as c, RenderService as d, StreamingErrorHandler as e };
@@ -759,4 +759,4 @@ declare function ErrorPageDevelopment({ error, viewPath, phase, }: ErrorPageDeve
759
759
  */
760
760
  declare function ErrorPageProduction(): react_jsx_runtime.JSX.Element;
761
761
 
762
- export { type ContextFactory as C, ErrorPageDevelopment as E, RenderModule as R, StreamingErrorHandler as S, TemplateParserService as T, RenderService as a, RenderInterceptor as b, type RenderConfig as c, type SSRMode as d, ErrorPageProduction as e };
762
+ export { type ContextFactory as C, ErrorPageDevelopment as E, type RenderConfig as R, type SSRMode as S, TemplateParserService as T, ErrorPageProduction as a, RenderInterceptor as b, RenderModule as c, RenderService as d, StreamingErrorHandler as e };
package/dist/index.d.mts CHANGED
@@ -1,7 +1,7 @@
1
- export { C as ContextFactory, E as ErrorPageDevelopment, e as ErrorPageProduction, c as RenderConfig, b as RenderInterceptor, R as RenderModule, a as RenderService, d as SSRMode, S as StreamingErrorHandler, T as TemplateParserService } from './index-ZpkYrPcK.mjs';
1
+ export { C as ContextFactory, E as ErrorPageDevelopment, a as ErrorPageProduction, R as RenderConfig, b as RenderInterceptor, c as RenderModule, d as RenderService, S as SSRMode, e as StreamingErrorHandler, T as TemplateParserService } from './index-DcpOFSp4.mjs';
2
2
  import React, { ComponentType, ReactNode } from 'react';
3
- import { P as PageProps } from './use-page-context-CVC9DHcL.mjs';
4
- export { a as PageContextProvider, c as createSSRHooks, i as useCookie, h as useCookies, g as useHeader, f as useHeaders, u as usePageContext, b as useParams, d as useQuery, e as useRequest } from './use-page-context-CVC9DHcL.mjs';
3
+ import { P as PageProps } from './use-page-context-CmxWHIK3.mjs';
4
+ export { a as PageContextProvider, c as createSSRHooks, u as useCookie, b as useCookies, d as useHeader, e as useHeaders, f as usePageContext, g as useParams, h as useQuery, i as useRequest } from './use-page-context-CmxWHIK3.mjs';
5
5
  import { R as RenderContext, H as HeadData, a as RenderResponse } from './render-response.interface-ClWJXKL4.mjs';
6
6
  import '@nestjs/common';
7
7
  import 'http';
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- export { C as ContextFactory, E as ErrorPageDevelopment, e as ErrorPageProduction, c as RenderConfig, b as RenderInterceptor, R as RenderModule, a as RenderService, d as SSRMode, S as StreamingErrorHandler, T as TemplateParserService } from './index-CSvZfKpi.js';
1
+ export { C as ContextFactory, E as ErrorPageDevelopment, a as ErrorPageProduction, R as RenderConfig, b as RenderInterceptor, c as RenderModule, d as RenderService, S as SSRMode, e as StreamingErrorHandler, T as TemplateParserService } from './index-CGfEDKI4.js';
2
2
  import React, { ComponentType, ReactNode } from 'react';
3
- import { P as PageProps } from './use-page-context-DChgHhL9.js';
4
- export { a as PageContextProvider, c as createSSRHooks, i as useCookie, h as useCookies, g as useHeader, f as useHeaders, u as usePageContext, b as useParams, d as useQuery, e as useRequest } from './use-page-context-DChgHhL9.js';
3
+ import { P as PageProps } from './use-page-context-CUV31oda.js';
4
+ export { a as PageContextProvider, c as createSSRHooks, u as useCookie, b as useCookies, d as useHeader, e as useHeaders, f as usePageContext, g as useParams, h as useQuery, i as useRequest } from './use-page-context-CUV31oda.js';
5
5
  import { R as RenderContext, H as HeadData, a as RenderResponse } from './render-response.interface-ClWJXKL4.js';
6
6
  import '@nestjs/common';
7
7
  import 'http';
package/dist/index.js CHANGED
@@ -1253,9 +1253,10 @@ exports.RenderInterceptor = class RenderInterceptor {
1253
1253
  if (typeof data === "string") {
1254
1254
  return data;
1255
1255
  }
1256
+ const requestPath = request.path ?? request.url?.split("?")[0] ?? "/";
1256
1257
  const renderContext = {
1257
1258
  url: request.url,
1258
- path: request.path,
1259
+ path: requestPath,
1259
1260
  query: request.query,
1260
1261
  params: request.params,
1261
1262
  method: request.method
package/dist/index.mjs CHANGED
@@ -1246,9 +1246,10 @@ var RenderInterceptor = class {
1246
1246
  if (typeof data === "string") {
1247
1247
  return data;
1248
1248
  }
1249
+ const requestPath = request.path ?? request.url?.split("?")[0] ?? "/";
1249
1250
  const renderContext = {
1250
1251
  url: request.url,
1251
- path: request.path,
1252
+ path: requestPath,
1252
1253
  query: request.query,
1253
1254
  params: request.params,
1254
1255
  method: request.method
@@ -1,4 +1,4 @@
1
- export { E as ErrorPageDevelopment, e as ErrorPageProduction, b as RenderInterceptor, R as RenderModule, a as RenderService, S as StreamingErrorHandler, T as TemplateParserService } from '../index-ZpkYrPcK.mjs';
1
+ export { E as ErrorPageDevelopment, a as ErrorPageProduction, b as RenderInterceptor, c as RenderModule, d as RenderService, e as StreamingErrorHandler, T as TemplateParserService } from '../index-DcpOFSp4.mjs';
2
2
  import '@nestjs/common';
3
3
  import 'react';
4
4
  import '../render-response.interface-ClWJXKL4.mjs';
@@ -1,4 +1,4 @@
1
- export { E as ErrorPageDevelopment, e as ErrorPageProduction, b as RenderInterceptor, R as RenderModule, a as RenderService, S as StreamingErrorHandler, T as TemplateParserService } from '../index-CSvZfKpi.js';
1
+ export { E as ErrorPageDevelopment, a as ErrorPageProduction, b as RenderInterceptor, c as RenderModule, d as RenderService, e as StreamingErrorHandler, T as TemplateParserService } from '../index-CGfEDKI4.js';
2
2
  import '@nestjs/common';
3
3
  import 'react';
4
4
  import '../render-response.interface-ClWJXKL4.js';
@@ -1234,9 +1234,10 @@ exports.RenderInterceptor = class RenderInterceptor {
1234
1234
  if (typeof data === "string") {
1235
1235
  return data;
1236
1236
  }
1237
+ const requestPath = request.path ?? request.url?.split("?")[0] ?? "/";
1237
1238
  const renderContext = {
1238
1239
  url: request.url,
1239
- path: request.path,
1240
+ path: requestPath,
1240
1241
  query: request.query,
1241
1242
  params: request.params,
1242
1243
  method: request.method
@@ -1228,9 +1228,10 @@ var RenderInterceptor = class {
1228
1228
  if (typeof data === "string") {
1229
1229
  return data;
1230
1230
  }
1231
+ const requestPath = request.path ?? request.url?.split("?")[0] ?? "/";
1231
1232
  const renderContext = {
1232
1233
  url: request.url,
1233
- path: request.path,
1234
+ path: requestPath,
1234
1235
  query: request.query,
1235
1236
  params: request.params,
1236
1237
  method: request.method
@@ -164,9 +164,29 @@ function composeWithLayout(
164
164
  return result;
165
165
  }
166
166
 
167
- // Build layouts array - use RootLayout if it exists (matching server behavior)
167
+ // Build layouts array from server-provided __LAYOUTS__ data
168
+ // This ensures controller-level layouts (e.g., @Layout(RecipesLayout)) are
169
+ // included during hydration on hard refresh, not just the auto-discovered root layout
170
+ const layoutsData = window.__LAYOUTS__ || [];
168
171
  const layouts: Array<{ layout: React.ComponentType<any>; props?: any }> = [];
169
- if (RootLayout) {
172
+
173
+ for (const { name: layoutName, props: layoutProps } of layoutsData) {
174
+ const layoutEntry = componentMap.find(
175
+ (c) =>
176
+ c.name === layoutName ||
177
+ c.normalizedFilename === layoutName ||
178
+ c.filename === layoutName.toLowerCase(),
179
+ );
180
+ if (layoutEntry) {
181
+ layouts.push({ layout: layoutEntry.component, props: layoutProps || {} });
182
+ } else if (layoutName === 'RootLayout' && RootLayout) {
183
+ // Fallback: if the auto-discovered root layout wasn't in componentMap by name
184
+ layouts.push({ layout: RootLayout, props: layoutProps || {} });
185
+ }
186
+ }
187
+
188
+ // Fallback: if no __LAYOUTS__ data, use auto-discovered RootLayout
189
+ if (layouts.length === 0 && RootLayout) {
170
190
  layouts.push({ layout: RootLayout, props: {} });
171
191
  }
172
192
 
@@ -279,4 +279,4 @@ declare const useHeader: (name: string) => string | undefined;
279
279
  declare const useCookies: () => Record<string, string>;
280
280
  declare const useCookie: (name: string) => string | undefined;
281
281
 
282
- export { type PageProps as P, PageContextProvider as a, useParams as b, createSSRHooks as c, useQuery as d, useRequest as e, useHeaders as f, useHeader as g, useCookies as h, useCookie as i, updatePageContext as j, usePageContext as u };
282
+ export { type PageProps as P, PageContextProvider as a, useCookies as b, createSSRHooks as c, useHeader as d, useHeaders as e, usePageContext as f, useParams as g, useQuery as h, useRequest as i, updatePageContext as j, useCookie as u };
@@ -279,4 +279,4 @@ declare const useHeader: (name: string) => string | undefined;
279
279
  declare const useCookies: () => Record<string, string>;
280
280
  declare const useCookie: (name: string) => string | undefined;
281
281
 
282
- export { type PageProps as P, PageContextProvider as a, useParams as b, createSSRHooks as c, useQuery as d, useRequest as e, useHeaders as f, useHeader as g, useCookies as h, useCookie as i, updatePageContext as j, usePageContext as u };
282
+ export { type PageProps as P, PageContextProvider as a, useCookies as b, createSSRHooks as c, useHeader as d, useHeaders as e, usePageContext as f, useParams as g, useQuery as h, useRequest as i, updatePageContext as j, useCookie as u };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nestjs-ssr/react",
3
- "version": "0.3.6",
3
+ "version": "0.3.8",
4
4
  "description": "React SSR for NestJS that respects Clean Architecture. Proper DI, SOLID principles, clear separation of concerns.",
5
5
  "keywords": [
6
6
  "nestjs",
@@ -90,6 +90,10 @@
90
90
  "test:integration:prod": "TEST_MODE=prod playwright test -c test/integration/playwright.config.ts",
91
91
  "test:integration:run": "pnpm test:integration:dev",
92
92
  "test:integration:clean": "rm -rf test/integration/fixtures/*/",
93
+ "test:e2e:setup": "tsx test/e2e/setup/create-fixtures.ts",
94
+ "test:e2e:dev": "TEST_MODE=dev playwright test -c test/e2e/playwright.config.ts",
95
+ "test:e2e:prod": "TEST_MODE=prod playwright test -c test/e2e/playwright.config.ts",
96
+ "test:e2e:clean": "rm -rf test/e2e/fixtures/*/",
93
97
  "size": "size-limit",
94
98
  "api:extract": "api-extractor run --local --verbose",
95
99
  "api:check": "api-extractor run --verbose",
@@ -109,11 +113,11 @@
109
113
  }
110
114
  ],
111
115
  "peerDependencies": {
116
+ "@fastify/static": "^8.0.0 || ^7.0.0",
112
117
  "@nestjs/common": "^11.0.0",
113
118
  "@nestjs/core": "^11.0.0",
114
119
  "@nestjs/platform-express": "^11.0.0",
115
120
  "@nestjs/platform-fastify": "^11.0.0",
116
- "@fastify/static": "^8.0.0 || ^7.0.0",
117
121
  "http-proxy-middleware": "^3.0.0 || ^2.0.0",
118
122
  "react": "^19.0.0",
119
123
  "react-dom": "^19.0.0",
@@ -135,38 +139,38 @@
135
139
  }
136
140
  },
137
141
  "dependencies": {
138
- "citty": "^0.1.6",
142
+ "citty": "^0.2.0",
139
143
  "consola": "^3.4.2",
140
- "devalue": "^5.6.1",
144
+ "devalue": "^5.6.2",
141
145
  "escape-html": "^1.0.3"
142
146
  },
143
147
  "devDependencies": {
144
- "@microsoft/api-extractor": "^7.55.2",
145
- "@nestjs/common": "^11.1.9",
146
- "@nestjs/core": "^11.1.9",
147
- "@nestjs/platform-express": "^11.1.9",
148
- "@nestjs/testing": "^11.1.9",
149
- "@playwright/test": "^1.57.0",
148
+ "@microsoft/api-extractor": "^7.56.3",
149
+ "@nestjs/common": "^11.1.13",
150
+ "@nestjs/core": "^11.1.13",
151
+ "@nestjs/platform-express": "^11.1.13",
152
+ "@nestjs/testing": "^11.1.13",
153
+ "@playwright/test": "^1.58.2",
150
154
  "@testing-library/jest-dom": "^6.9.1",
151
- "@testing-library/react": "^16.3.0",
155
+ "@testing-library/react": "^16.3.2",
152
156
  "@types/escape-html": "^1.0.4",
153
157
  "@types/express": "^5.0.6",
154
- "@types/node": "^25.0.1",
155
- "@types/react": "^19.2.7",
158
+ "@types/node": "^25.2.2",
159
+ "@types/react": "^19.2.13",
156
160
  "@types/react-dom": "^19.2.3",
157
161
  "@types/supertest": "^6.0.3",
158
- "@vitejs/plugin-react": "^5.1.2",
159
- "@vitest/coverage-v8": "^4.0.15",
160
- "@vitest/ui": "^4.0.15",
161
- "happy-dom": "^20.0.11",
162
- "react": "^19.2.3",
163
- "react-dom": "^19.2.3",
162
+ "@vitejs/plugin-react": "^5.1.3",
163
+ "@vitest/coverage-v8": "^4.0.18",
164
+ "@vitest/ui": "^4.0.18",
165
+ "happy-dom": "^20.5.0",
166
+ "react": "^19.2.4",
167
+ "react-dom": "^19.2.4",
164
168
  "rxjs": "^7.8.2",
165
- "supertest": "^7.1.4",
169
+ "supertest": "^7.2.2",
166
170
  "tsup": "^8.5.1",
167
171
  "typescript": "^5.9.3",
168
- "vite": "^7.2.7",
169
- "vitest": "^4.0.15"
172
+ "vite": "^7.3.1",
173
+ "vitest": "^4.0.18"
170
174
  },
171
175
  "publishConfig": {
172
176
  "access": "public"
@@ -164,9 +164,29 @@ function composeWithLayout(
164
164
  return result;
165
165
  }
166
166
 
167
- // Build layouts array - use RootLayout if it exists (matching server behavior)
167
+ // Build layouts array from server-provided __LAYOUTS__ data
168
+ // This ensures controller-level layouts (e.g., @Layout(RecipesLayout)) are
169
+ // included during hydration on hard refresh, not just the auto-discovered root layout
170
+ const layoutsData = window.__LAYOUTS__ || [];
168
171
  const layouts: Array<{ layout: React.ComponentType<any>; props?: any }> = [];
169
- if (RootLayout) {
172
+
173
+ for (const { name: layoutName, props: layoutProps } of layoutsData) {
174
+ const layoutEntry = componentMap.find(
175
+ (c) =>
176
+ c.name === layoutName ||
177
+ c.normalizedFilename === layoutName ||
178
+ c.filename === layoutName.toLowerCase(),
179
+ );
180
+ if (layoutEntry) {
181
+ layouts.push({ layout: layoutEntry.component, props: layoutProps || {} });
182
+ } else if (layoutName === 'RootLayout' && RootLayout) {
183
+ // Fallback: if the auto-discovered root layout wasn't in componentMap by name
184
+ layouts.push({ layout: RootLayout, props: layoutProps || {} });
185
+ }
186
+ }
187
+
188
+ // Fallback: if no __LAYOUTS__ data, use auto-discovered RootLayout
189
+ if (layouts.length === 0 && RootLayout) {
170
190
  layouts.push({ layout: RootLayout, props: {} });
171
191
  }
172
192