@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 +56 -42
- package/dist/client.d.mts +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/{index-CSvZfKpi.d.ts → index-CGfEDKI4.d.ts} +1 -1
- package/dist/{index-ZpkYrPcK.d.mts → index-DcpOFSp4.d.mts} +1 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -1
- package/dist/index.mjs +2 -1
- package/dist/render/index.d.mts +1 -1
- package/dist/render/index.d.ts +1 -1
- package/dist/render/index.js +2 -1
- package/dist/render/index.mjs +2 -1
- package/dist/templates/entry-client.tsx +22 -2
- package/dist/{use-page-context-DChgHhL9.d.ts → use-page-context-CUV31oda.d.ts} +1 -1
- package/dist/{use-page-context-CVC9DHcL.d.mts → use-page-context-CmxWHIK3.d.mts} +1 -1
- package/package.json +26 -22
- package/src/templates/entry-client.tsx +22 -2
package/README.md
CHANGED
|
@@ -3,70 +3,82 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@nestjs-ssr/react)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
React
|
|
6
|
+
**React SSR for NestJS. One app. One deploy. Full type safety from database to DOM.**
|
|
7
7
|
|
|
8
|
-
|
|
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
|
-
|
|
10
|
+
**[Documentation](https://georgialexandrov.github.io/nestjs-ssr/)** | **[Get Started](https://georgialexandrov.github.io/nestjs-ssr/guide/installation)**
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
npx @nestjs-ssr/react init
|
|
14
|
-
```
|
|
12
|
+
## The whole contract in two files
|
|
15
13
|
|
|
16
14
|
```typescript
|
|
17
|
-
|
|
18
|
-
@
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
56
|
+
Return the wrong shape and TypeScript catches it before the code runs.
|
|
33
57
|
|
|
34
|
-
##
|
|
58
|
+
## Nothing breaks
|
|
35
59
|
|
|
36
|
-
|
|
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
|
-
##
|
|
62
|
+
## Everything improves
|
|
45
63
|
|
|
46
|
-
**
|
|
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
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
- Whitelist what reaches the client
|
|
57
|
-
|
|
58
|
-
**Development:**
|
|
73
|
+
```bash
|
|
74
|
+
npx @nestjs-ssr/react init
|
|
75
|
+
```
|
|
59
76
|
|
|
60
|
-
|
|
61
|
-
- Separate mode: standalone Vite server, true HMR
|
|
77
|
+
One command. Works with Express and Fastify.
|
|
62
78
|
|
|
63
79
|
## Requirements
|
|
64
80
|
|
|
65
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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-
|
|
4
|
-
export { a as PageContextProvider, c as createSSRHooks,
|
|
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,
|
|
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-
|
|
4
|
-
export { a as PageContextProvider, c as createSSRHooks,
|
|
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:
|
|
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:
|
|
1252
|
+
path: requestPath,
|
|
1252
1253
|
query: request.query,
|
|
1253
1254
|
params: request.params,
|
|
1254
1255
|
method: request.method
|
package/dist/render/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { E as ErrorPageDevelopment,
|
|
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';
|
package/dist/render/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { E as ErrorPageDevelopment,
|
|
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';
|
package/dist/render/index.js
CHANGED
|
@@ -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:
|
|
1240
|
+
path: requestPath,
|
|
1240
1241
|
query: request.query,
|
|
1241
1242
|
params: request.params,
|
|
1242
1243
|
method: request.method
|
package/dist/render/index.mjs
CHANGED
|
@@ -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:
|
|
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 -
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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.
|
|
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.
|
|
142
|
+
"citty": "^0.2.0",
|
|
139
143
|
"consola": "^3.4.2",
|
|
140
|
-
"devalue": "^5.6.
|
|
144
|
+
"devalue": "^5.6.2",
|
|
141
145
|
"escape-html": "^1.0.3"
|
|
142
146
|
},
|
|
143
147
|
"devDependencies": {
|
|
144
|
-
"@microsoft/api-extractor": "^7.
|
|
145
|
-
"@nestjs/common": "^11.1.
|
|
146
|
-
"@nestjs/core": "^11.1.
|
|
147
|
-
"@nestjs/platform-express": "^11.1.
|
|
148
|
-
"@nestjs/testing": "^11.1.
|
|
149
|
-
"@playwright/test": "^1.
|
|
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.
|
|
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.
|
|
155
|
-
"@types/react": "^19.2.
|
|
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.
|
|
159
|
-
"@vitest/coverage-v8": "^4.0.
|
|
160
|
-
"@vitest/ui": "^4.0.
|
|
161
|
-
"happy-dom": "^20.0
|
|
162
|
-
"react": "^19.2.
|
|
163
|
-
"react-dom": "^19.2.
|
|
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.
|
|
169
|
+
"supertest": "^7.2.2",
|
|
166
170
|
"tsup": "^8.5.1",
|
|
167
171
|
"typescript": "^5.9.3",
|
|
168
|
-
"vite": "^7.
|
|
169
|
-
"vitest": "^4.0.
|
|
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 -
|
|
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
|
-
|
|
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
|
|