vite-plugin-ssr-config 1.0.5 → 1.1.0-beta1
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 +86 -3
- package/dist/index.cjs +37 -27
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +37 -27
- package/package.json +12 -9
- package/ssr/entryClient.jsx +16 -7
- package/ssr/entryRender.jsx +23 -25
- package/ssr/handler.js +9 -3
- package/ssr/pageBrowser.jsx +10 -24
- package/ssr/pageServer.jsx +22 -39
- package/ssr/root.jsx +4 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# vite-plugin-ssr-config
|
|
2
2
|
|
|
3
|
-
[vite-plugin-ssr-config](https://github.com/yracnet/vite-plugin-ssr-config) configures server-side rendering (SSR) with Vite, providing essential setups and options for building both client and server bundles, along with necessary React components for SSR, specifically for [React](https://reactjs.org/ "react - A JavaScript library for building user interfaces"), [React Router](https://reactrouter.com/ "react-router - Declarative routing for React"),
|
|
3
|
+
[vite-plugin-ssr-config](https://github.com/yracnet/vite-plugin-ssr-config) configures server-side rendering (SSR) with Vite, providing essential setups and options for building both client and server bundles, along with necessary React components for SSR, specifically for [React](https://reactjs.org/ "react - A JavaScript library for building user interfaces"), [React Router](https://reactrouter.com/ "react-router - Declarative routing for React"), [React Query](https://tanstack.com/query "@tanstack/react-query - Data fetching and caching library"), and [react-slotx](https://github.com/react-slotx/react-slotx "react-slotx - Slot-based content management for React SSR").
|
|
4
4
|
|
|
5
5
|
## Additional Resources
|
|
6
6
|
|
|
@@ -10,6 +10,7 @@ For more detailed information and resources related to `vite-plugin-ssr-config`,
|
|
|
10
10
|
- **GitHub Repository**: [yracnet/vite-plugin-ssr-config](https://github.com/yracnet/vite-plugin-ssr-config)
|
|
11
11
|
- **Dev.to Article**: [Create an SSR Application with Vite, React, React Query and React Router](https://dev.to/yracnet/create-an-ssr-application-with-vite-react-react-query-and-react-router-2dd5)
|
|
12
12
|
- **Tutorial**: [Tutorial](./tutorial.md)
|
|
13
|
+
- **react-slotx Documentation**: [react-slotx GitHub](https://github.com/react-slotx/react-slotx)
|
|
13
14
|
|
|
14
15
|
## Install
|
|
15
16
|
|
|
@@ -19,15 +20,16 @@ To add this plugin to your project, run the following commands:
|
|
|
19
20
|
yarn add vite-plugin-ssr-config vite-plugin-web-routes -D
|
|
20
21
|
```
|
|
21
22
|
```bash
|
|
22
|
-
yarn add react-query react-router express
|
|
23
|
+
yarn add @tanstack/react-query react-router express react-slotx
|
|
23
24
|
```
|
|
24
25
|
|
|
25
26
|
This will install:
|
|
26
27
|
|
|
27
28
|
- vite-plugin-ssr-config: The plugin for server-side rendering (SSR) with Vite.
|
|
28
29
|
- vite-plugin-web-routes: Automatically generate route files for your pages.
|
|
29
|
-
- react-query: Delegate Hydrated State
|
|
30
|
+
- @tanstack/react-query: Delegate Hydrated State
|
|
30
31
|
- react-router: The routing library for React, used to manage navigation within the app.
|
|
32
|
+
- react-slotx: Slot-based content management system for SSR, enabling dynamic head and SEO optimization.
|
|
31
33
|
|
|
32
34
|
|
|
33
35
|
## Basic Configuration Example
|
|
@@ -266,3 +268,84 @@ These custom commands are designed to provide flexibility in your Vite SSR workf
|
|
|
266
268
|
## Use Case
|
|
267
269
|
|
|
268
270
|
This plugin is intended for projects that require SSR with Vite, specifically React apps. It helps in managing SSR entry files, routing, page rendering, and output structure for both server and client builds.
|
|
271
|
+
|
|
272
|
+
# SEO with react-slotx
|
|
273
|
+
|
|
274
|
+
The plugin integrates [react-slotx](https://github.com/yracnet/react-slotx) for dynamic head content management. It lets any page component inject `<title>`, `<meta>`, and other head elements that are rendered server-side and kept reactive on the client.
|
|
275
|
+
|
|
276
|
+
## How it works
|
|
277
|
+
|
|
278
|
+
| Part | Import | Role |
|
|
279
|
+
|---|---|---|
|
|
280
|
+
| `Slot` | `react-slotx` | Register content into a named slot from any component |
|
|
281
|
+
| `Outlet` | `react-slotx` | Render slot content at a specific location in the tree |
|
|
282
|
+
| `SlotProvider` | `react-slotx` | Context provider — wraps the app (handled by the plugin) |
|
|
283
|
+
| `SlotClient` | `react-slotx` | Client-side slot store (handled by the plugin) |
|
|
284
|
+
| `SlotSSRClient` | `react-slotx/server` | Server-side slot store with `renderToString` (handled by the plugin) |
|
|
285
|
+
|
|
286
|
+
The plugin's default `root.jsx` already places an `<Outlet name="head" />` inside `<head>`. You only need to use `<Slot>` in your pages.
|
|
287
|
+
|
|
288
|
+
## Root document
|
|
289
|
+
|
|
290
|
+
```jsx
|
|
291
|
+
// ssr/root.jsx
|
|
292
|
+
import { LiveReload } from "@ssr/liveReload.jsx";
|
|
293
|
+
import { ViteScripts } from "@ssr/viteScripts.jsx";
|
|
294
|
+
import { Outlet as OutletSlot } from "react-slotx";
|
|
295
|
+
import { Outlet as OutletRoutes } from "react-router";
|
|
296
|
+
|
|
297
|
+
export const RootDocument = () => {
|
|
298
|
+
return (
|
|
299
|
+
<html lang="en">
|
|
300
|
+
<head>
|
|
301
|
+
<meta charSet="utf-8" />
|
|
302
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
303
|
+
<LiveReload />
|
|
304
|
+
<OutletSlot name="head" />
|
|
305
|
+
</head>
|
|
306
|
+
<body>
|
|
307
|
+
<OutletRoutes />
|
|
308
|
+
<ViteScripts />
|
|
309
|
+
</body>
|
|
310
|
+
</html>
|
|
311
|
+
);
|
|
312
|
+
};
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
## Page usage
|
|
316
|
+
|
|
317
|
+
```jsx
|
|
318
|
+
import { Slot } from "react-slotx";
|
|
319
|
+
|
|
320
|
+
export default function PostPage() {
|
|
321
|
+
const { data = {} } = useQuery(...);
|
|
322
|
+
return (
|
|
323
|
+
<>
|
|
324
|
+
<Slot name="head">
|
|
325
|
+
{/* title support only string content */}
|
|
326
|
+
<title>{`${data.title} — My App`}</title>
|
|
327
|
+
<meta name="description" content={data.body} />
|
|
328
|
+
<meta property="og:title" content={data.title} />
|
|
329
|
+
</Slot>
|
|
330
|
+
{/* page content */}
|
|
331
|
+
</>
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
The `<Slot name="head">` content is extracted server-side via `slotClient.renderToString("head")` and injected into the HTML stream before `</head>`. On the client it stays reactive through `<OutletSlot name="head" />`.
|
|
337
|
+
|
|
338
|
+
## Slot props
|
|
339
|
+
|
|
340
|
+
| Prop | Type | Default | Description |
|
|
341
|
+
|---|---|---|---|
|
|
342
|
+
| `name` | `string` | `"default"` | Slot name — must match the `name` on `<Outlet>` |
|
|
343
|
+
| `priority` | `number` | `1` | When multiple `<Slot>` share a name, the highest priority wins (in `"priority"` mode) |
|
|
344
|
+
| `dangerouslyEnableRender` | `boolean` | `false` | Also render children in-place (not just in the outlet) |
|
|
345
|
+
|
|
346
|
+
## Outlet props
|
|
347
|
+
|
|
348
|
+
| Prop | Type | Default | Description |
|
|
349
|
+
|---|---|---|---|
|
|
350
|
+
| `name` | `string \| "*"` | `"default"` | Which slot to render. `"*"` renders all slots |
|
|
351
|
+
| `mode` | `"priority" \| "first" \| "last" \| "all"` | `"priority"` | How to resolve multiple slots with the same name |
|
package/dist/index.cjs
CHANGED
|
@@ -41,6 +41,7 @@ var import_path5 = __toESM(require("path"), 1);
|
|
|
41
41
|
var assertSSRConfig = (ssrOpts = {}) => {
|
|
42
42
|
let {
|
|
43
43
|
root = process.cwd(),
|
|
44
|
+
version = "1.1",
|
|
44
45
|
disableBuild = false,
|
|
45
46
|
//Main Entry
|
|
46
47
|
entryClient = ".ssr/entryClient.jsx",
|
|
@@ -67,6 +68,7 @@ var assertSSRConfig = (ssrOpts = {}) => {
|
|
|
67
68
|
} = ssrOpts;
|
|
68
69
|
return {
|
|
69
70
|
root,
|
|
71
|
+
version,
|
|
70
72
|
disableBuild,
|
|
71
73
|
entryClient,
|
|
72
74
|
entryRender,
|
|
@@ -412,6 +414,7 @@ var pluginResolve = (ssrConfig2) => {
|
|
|
412
414
|
};
|
|
413
415
|
|
|
414
416
|
// src/plugin-serve/index.ts
|
|
417
|
+
var import_express = __toESM(require("express"), 1);
|
|
415
418
|
var import_fs_extra2 = __toESM(require("fs-extra"), 1);
|
|
416
419
|
var import_path4 = __toESM(require("path"), 1);
|
|
417
420
|
var pluginServe = (ssrConfig2) => {
|
|
@@ -431,33 +434,40 @@ var pluginServe = (ssrConfig2) => {
|
|
|
431
434
|
};
|
|
432
435
|
},
|
|
433
436
|
configureServer: async (devServer) => {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
437
|
+
const appProxy = (0, import_express.default)();
|
|
438
|
+
appProxy.use(async (req, res, next) => {
|
|
439
|
+
if (req.method !== "GET") {
|
|
440
|
+
return next();
|
|
441
|
+
}
|
|
442
|
+
const indexHtmlPath = import_path4.default.join(root, req.url, "index.html");
|
|
443
|
+
if (import_fs_extra2.default.existsSync(indexHtmlPath)) {
|
|
444
|
+
return devServer.transformIndexHtml(
|
|
445
|
+
req.url,
|
|
446
|
+
import_fs_extra2.default.readFileSync(indexHtmlPath, "utf-8")
|
|
447
|
+
).then((html) => {
|
|
448
|
+
res.setHeader("Content-Type", "text/html");
|
|
449
|
+
res.setHeader("Pragma", "no-cache");
|
|
450
|
+
res.setHeader("Expires", "0");
|
|
451
|
+
res.end(html);
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
next();
|
|
455
|
+
});
|
|
456
|
+
appProxy.use(async (req, res, next) => {
|
|
457
|
+
try {
|
|
458
|
+
const mod = await devServer.ssrLoadModule("@ssr/handler.js", {
|
|
459
|
+
fixStacktrace: true
|
|
460
|
+
});
|
|
461
|
+
await mod.handler(req, res, next);
|
|
462
|
+
} catch (error) {
|
|
463
|
+
devServer.ssrFixStacktrace(error);
|
|
464
|
+
process.exitCode = 1;
|
|
465
|
+
next(error);
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
return () => {
|
|
469
|
+
process.env.SSR = true;
|
|
470
|
+
devServer.middlewares.use(appProxy);
|
|
461
471
|
};
|
|
462
472
|
}
|
|
463
473
|
};
|
package/dist/index.d.cts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import path5 from "path";
|
|
|
5
5
|
var assertSSRConfig = (ssrOpts = {}) => {
|
|
6
6
|
let {
|
|
7
7
|
root = process.cwd(),
|
|
8
|
+
version = "1.1",
|
|
8
9
|
disableBuild = false,
|
|
9
10
|
//Main Entry
|
|
10
11
|
entryClient = ".ssr/entryClient.jsx",
|
|
@@ -31,6 +32,7 @@ var assertSSRConfig = (ssrOpts = {}) => {
|
|
|
31
32
|
} = ssrOpts;
|
|
32
33
|
return {
|
|
33
34
|
root,
|
|
35
|
+
version,
|
|
34
36
|
disableBuild,
|
|
35
37
|
entryClient,
|
|
36
38
|
entryRender,
|
|
@@ -375,6 +377,7 @@ var pluginResolve = (ssrConfig2) => {
|
|
|
375
377
|
};
|
|
376
378
|
|
|
377
379
|
// src/plugin-serve/index.ts
|
|
380
|
+
import express from "express";
|
|
378
381
|
import fs2 from "fs-extra";
|
|
379
382
|
import path4 from "path";
|
|
380
383
|
var pluginServe = (ssrConfig2) => {
|
|
@@ -394,33 +397,40 @@ var pluginServe = (ssrConfig2) => {
|
|
|
394
397
|
};
|
|
395
398
|
},
|
|
396
399
|
configureServer: async (devServer) => {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
400
|
+
const appProxy = express();
|
|
401
|
+
appProxy.use(async (req, res, next) => {
|
|
402
|
+
if (req.method !== "GET") {
|
|
403
|
+
return next();
|
|
404
|
+
}
|
|
405
|
+
const indexHtmlPath = path4.join(root, req.url, "index.html");
|
|
406
|
+
if (fs2.existsSync(indexHtmlPath)) {
|
|
407
|
+
return devServer.transformIndexHtml(
|
|
408
|
+
req.url,
|
|
409
|
+
fs2.readFileSync(indexHtmlPath, "utf-8")
|
|
410
|
+
).then((html) => {
|
|
411
|
+
res.setHeader("Content-Type", "text/html");
|
|
412
|
+
res.setHeader("Pragma", "no-cache");
|
|
413
|
+
res.setHeader("Expires", "0");
|
|
414
|
+
res.end(html);
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
next();
|
|
418
|
+
});
|
|
419
|
+
appProxy.use(async (req, res, next) => {
|
|
420
|
+
try {
|
|
421
|
+
const mod = await devServer.ssrLoadModule("@ssr/handler.js", {
|
|
422
|
+
fixStacktrace: true
|
|
423
|
+
});
|
|
424
|
+
await mod.handler(req, res, next);
|
|
425
|
+
} catch (error) {
|
|
426
|
+
devServer.ssrFixStacktrace(error);
|
|
427
|
+
process.exitCode = 1;
|
|
428
|
+
next(error);
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
return () => {
|
|
432
|
+
process.env.SSR = true;
|
|
433
|
+
devServer.middlewares.use(appProxy);
|
|
424
434
|
};
|
|
425
435
|
}
|
|
426
436
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-plugin-ssr-config",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0-beta1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "A powerful Vite plugin designed to enable Server-Side Rendering (SSR) for React applications. It provides a comprehensive solution for bundling both SSR and CSR, with built-in support for React Router and React Query for efficient API handling and page rendering.",
|
|
6
6
|
"keywords": [
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"react",
|
|
11
11
|
"react-router",
|
|
12
12
|
"react-query",
|
|
13
|
+
"react-slotx",
|
|
13
14
|
"server",
|
|
14
15
|
"express"
|
|
15
16
|
],
|
|
@@ -34,28 +35,30 @@
|
|
|
34
35
|
"homepage": "https://github.com/yracnet/vite-plugin-ssr-config",
|
|
35
36
|
"bugs": "https://github.com/yracnet/vite-plugin-ssr-config/issues",
|
|
36
37
|
"scripts": {
|
|
38
|
+
"dev": "tsup --watch",
|
|
37
39
|
"build": "tsup",
|
|
38
40
|
"prepublish": "yarn build",
|
|
39
41
|
"prepack": "yarn build"
|
|
40
42
|
},
|
|
41
43
|
"dependencies": {
|
|
42
|
-
"
|
|
43
|
-
"fs-extra": "^11.2.0",
|
|
44
|
+
"fs-extra": "^11.3.5",
|
|
44
45
|
"react": "^19.2.7",
|
|
45
46
|
"react-dom": "^19.2.7",
|
|
46
47
|
"react-router": "^7.17.0",
|
|
47
48
|
"react-router-dom": "^7.17.0"
|
|
48
49
|
},
|
|
49
50
|
"peerDependencies": {
|
|
51
|
+
"dotenv-local": "^1.0.3",
|
|
52
|
+
"react-slotx": "^0.1.1",
|
|
50
53
|
"vite": ">=4.0.0"
|
|
51
54
|
},
|
|
52
55
|
"devDependencies": {
|
|
53
|
-
"@types/express": "^5.0.
|
|
56
|
+
"@types/express": "^5.0.6",
|
|
54
57
|
"@types/fs-extra": "^11.0.4",
|
|
55
|
-
"@types/node": "^
|
|
56
|
-
"express": "^
|
|
57
|
-
"tsup": "^8.
|
|
58
|
-
"typescript": "^
|
|
59
|
-
"vite": "^
|
|
58
|
+
"@types/node": "^25.9.3",
|
|
59
|
+
"express": "^5.2.1",
|
|
60
|
+
"tsup": "^8.5.1",
|
|
61
|
+
"typescript": "^6.0.3",
|
|
62
|
+
"vite": "^8.0.16"
|
|
60
63
|
}
|
|
61
64
|
}
|
package/ssr/entryClient.jsx
CHANGED
|
@@ -1,18 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { QueryClient, hydrate } from "@tanstack/react-query";
|
|
2
2
|
import { startTransition, StrictMode } from "react";
|
|
3
3
|
import { hydrateRoot } from "react-dom/client";
|
|
4
|
+
import { PageBrowser } from "./pageBrowser.jsx";
|
|
5
|
+
import { SlotClient } from "react-slotx";
|
|
4
6
|
|
|
5
7
|
startTransition(() => {
|
|
8
|
+
const queryClient = new QueryClient({
|
|
9
|
+
defaultOptions: {
|
|
10
|
+
queries: {
|
|
11
|
+
suspense: false,
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
const slotClient = new SlotClient();
|
|
6
16
|
const hydratedState = JSON.parse(atob(window.__HYDRATED_STATE__));
|
|
7
|
-
|
|
8
|
-
throw Error("Changes Not Allowed");
|
|
9
|
-
};
|
|
17
|
+
hydrate(queryClient, hydratedState);
|
|
10
18
|
hydrateRoot(
|
|
11
19
|
document,
|
|
12
20
|
<StrictMode>
|
|
13
21
|
<PageBrowser
|
|
14
|
-
|
|
15
|
-
|
|
22
|
+
basename={process.env.SSR_BASENAME}
|
|
23
|
+
queryClient={queryClient}
|
|
24
|
+
slotClient={slotClient}
|
|
16
25
|
/>
|
|
17
26
|
</StrictMode>,
|
|
18
27
|
{
|
|
@@ -20,6 +29,6 @@ startTransition(() => {
|
|
|
20
29
|
const logs = componentStack?.split("\n");
|
|
21
30
|
console.log("Error:", error, logs);
|
|
22
31
|
},
|
|
23
|
-
}
|
|
32
|
+
},
|
|
24
33
|
);
|
|
25
34
|
});
|
package/ssr/entryRender.jsx
CHANGED
|
@@ -1,28 +1,26 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { dehydrate, QueryClient } from "@tanstack/react-query";
|
|
2
2
|
import { StrictMode } from "react";
|
|
3
|
-
import { Transform } from "stream";
|
|
4
3
|
import { renderToPipeableStream } from "react-dom/server";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
state: {},
|
|
9
|
-
setHydratedState(partial) {
|
|
10
|
-
this.state = {
|
|
11
|
-
...this.state,
|
|
12
|
-
...partial,
|
|
13
|
-
};
|
|
14
|
-
},
|
|
15
|
-
};
|
|
16
|
-
};
|
|
4
|
+
import { Transform } from "stream";
|
|
5
|
+
import { PageServer } from "./pageServer.jsx";
|
|
6
|
+
import { SlotSSRClient } from "react-slotx/server";
|
|
17
7
|
|
|
18
8
|
const renderDefault = async (request, response, next) => {
|
|
19
|
-
const
|
|
9
|
+
const queryClient = new QueryClient({
|
|
10
|
+
defaultOptions: {
|
|
11
|
+
queries: {
|
|
12
|
+
suspense: true,
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
const slotClient = new SlotSSRClient();
|
|
20
17
|
const { pipe } = renderToPipeableStream(
|
|
21
18
|
<StrictMode>
|
|
22
19
|
<PageServer
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
basename={process.env.SSR_BASENAME}
|
|
21
|
+
location={request.originalUrl}
|
|
22
|
+
queryClient={queryClient}
|
|
23
|
+
slotClient={slotClient}
|
|
26
24
|
/>
|
|
27
25
|
</StrictMode>,
|
|
28
26
|
{
|
|
@@ -33,15 +31,19 @@ const renderDefault = async (request, response, next) => {
|
|
|
33
31
|
onAllReady: () => {
|
|
34
32
|
// console.log(request.originalUrl, "onAllReady");
|
|
35
33
|
let injected = false;
|
|
34
|
+
const state = dehydrate(queryClient);
|
|
35
|
+
const headTags = slotClient.renderToString("head");
|
|
36
|
+
const stateScript = `<script>window.__HYDRATED_STATE__ = "${btoa(JSON.stringify(state))}";</script>`;
|
|
37
|
+
const injectString = ["", headTags, stateScript, ""]
|
|
38
|
+
.join("\n");
|
|
36
39
|
const transform = new Transform({
|
|
37
40
|
transform(chunk, encoding, callback) {
|
|
38
41
|
if (!injected) {
|
|
39
|
-
const stateScript = `<script>window.__HYDRATED_STATE__ = "${btoa(JSON.stringify(context.state))}";</script>`;
|
|
40
42
|
const str = chunk.toString();
|
|
41
43
|
const idx = str.lastIndexOf("</head>");
|
|
42
44
|
if (idx !== -1) {
|
|
43
45
|
injected = true;
|
|
44
|
-
const out = str.slice(0, idx) +
|
|
46
|
+
const out = str.slice(0, idx) + injectString + str.slice(idx);
|
|
45
47
|
callback(null, out);
|
|
46
48
|
return;
|
|
47
49
|
}
|
|
@@ -49,11 +51,7 @@ const renderDefault = async (request, response, next) => {
|
|
|
49
51
|
callback(null, chunk);
|
|
50
52
|
},
|
|
51
53
|
});
|
|
52
|
-
|
|
53
|
-
response.setHeader("content-type", "text/html");
|
|
54
|
-
} catch (error) {
|
|
55
|
-
console.error("Set-Header Context-Type text/html Error:", error);
|
|
56
|
-
}
|
|
54
|
+
response.setHeader("content-type", "text/html");
|
|
57
55
|
transform.pipe(response);
|
|
58
56
|
pipe(transform);
|
|
59
57
|
},
|
package/ssr/handler.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
import express from "express";
|
|
1
2
|
import { render } from "@ssr/entryRender.jsx";
|
|
2
3
|
|
|
4
|
+
const ignored = [
|
|
5
|
+
/^\/%3Canonymous%20code%3E$/,
|
|
6
|
+
/\.js\.map$/,
|
|
7
|
+
/\.css\.map$/,
|
|
8
|
+
];
|
|
9
|
+
|
|
3
10
|
export const handler = async (req, res, next) => {
|
|
4
|
-
|
|
5
|
-
if (req.url === "/%3Canonymous%20code%3E" || req.url.endsWith(".js.map")) {
|
|
11
|
+
if (req.method !== "GET" || ignored.some((pattern) => pattern.test(req.path))) {
|
|
6
12
|
return next();
|
|
7
13
|
}
|
|
8
14
|
try {
|
|
@@ -10,4 +16,4 @@ export const handler = async (req, res, next) => {
|
|
|
10
16
|
} catch (error) {
|
|
11
17
|
next(error);
|
|
12
18
|
}
|
|
13
|
-
};
|
|
19
|
+
};
|
package/ssr/pageBrowser.jsx
CHANGED
|
@@ -1,34 +1,20 @@
|
|
|
1
1
|
import { ErrorBoundary } from "@ssr/errorBoundary.jsx";
|
|
2
2
|
import { RootRoutes } from "@ssr/rootRoutes.jsx";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { QueryClientProvider } from "@tanstack/react-query";
|
|
4
|
+
import { Suspense } from "react";
|
|
5
5
|
import { BrowserRouter } from "react-router";
|
|
6
|
+
import { SlotProvider } from "react-slotx";
|
|
6
7
|
|
|
7
|
-
const
|
|
8
|
-
defaultOptions: {
|
|
9
|
-
queries: {
|
|
10
|
-
suspense: false,
|
|
11
|
-
},
|
|
12
|
-
},
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
export const PageBrowser = ({ hydratedState = {}, setHydratedState }) => {
|
|
16
|
-
hydrate(queryClient, hydratedState);
|
|
8
|
+
export const PageBrowser = ({ basename, queryClient, slotClient }) => {
|
|
17
9
|
return (
|
|
18
|
-
<ErrorBoundary>
|
|
10
|
+
<ErrorBoundary suppressHydrationWarning={true}>
|
|
19
11
|
<QueryClientProvider client={queryClient}>
|
|
20
12
|
<Suspense>
|
|
21
|
-
<
|
|
22
|
-
basename={
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
<RootRoutes
|
|
27
|
-
hydratedState={hydratedState}
|
|
28
|
-
setHydratedState={setHydratedState}
|
|
29
|
-
/>
|
|
30
|
-
</StrictMode>
|
|
31
|
-
</BrowserRouter>
|
|
13
|
+
<SlotProvider client={slotClient}>
|
|
14
|
+
<BrowserRouter basename={basename}>
|
|
15
|
+
<RootRoutes />
|
|
16
|
+
</BrowserRouter>
|
|
17
|
+
</SlotProvider>
|
|
32
18
|
</Suspense>
|
|
33
19
|
</QueryClientProvider>
|
|
34
20
|
</ErrorBoundary>
|
package/ssr/pageServer.jsx
CHANGED
|
@@ -1,39 +1,22 @@
|
|
|
1
|
-
import { ErrorBoundary } from "@ssr/errorBoundary.jsx";
|
|
2
|
-
import { RootRoutes } from "@ssr/rootRoutes.jsx";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { StaticRouter } from "react-router";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
<StaticRouter
|
|
24
|
-
basename={process.env.SSR_BASENAME}
|
|
25
|
-
location={path}
|
|
26
|
-
future={{ v7_startTransition: true, v7_relativeSplatPath: true }}
|
|
27
|
-
>
|
|
28
|
-
<StrictMode>
|
|
29
|
-
<RootRoutes
|
|
30
|
-
hydratedState={hydratedState}
|
|
31
|
-
setHydratedState={setHydratedState}
|
|
32
|
-
/>
|
|
33
|
-
</StrictMode>
|
|
34
|
-
</StaticRouter>
|
|
35
|
-
</Suspense>
|
|
36
|
-
</QueryClientProvider>
|
|
37
|
-
</ErrorBoundary>
|
|
38
|
-
);
|
|
39
|
-
};
|
|
1
|
+
import { ErrorBoundary } from "@ssr/errorBoundary.jsx";
|
|
2
|
+
import { RootRoutes } from "@ssr/rootRoutes.jsx";
|
|
3
|
+
import { QueryClientProvider } from "@tanstack/react-query";
|
|
4
|
+
import { Suspense } from "react";
|
|
5
|
+
import { StaticRouter } from "react-router";
|
|
6
|
+
import { SlotProvider } from "react-slotx";
|
|
7
|
+
|
|
8
|
+
export const PageServer = ({ basename, location, queryClient, slotClient }) => {
|
|
9
|
+
return (
|
|
10
|
+
<ErrorBoundary suppressHydrationWarning={true}>
|
|
11
|
+
<QueryClientProvider client={queryClient}>
|
|
12
|
+
<Suspense>
|
|
13
|
+
<SlotProvider client={slotClient}>
|
|
14
|
+
<StaticRouter basename={basename} location={location}>
|
|
15
|
+
<RootRoutes />
|
|
16
|
+
</StaticRouter>
|
|
17
|
+
</SlotProvider>
|
|
18
|
+
</Suspense>
|
|
19
|
+
</QueryClientProvider>
|
|
20
|
+
</ErrorBoundary>
|
|
21
|
+
);
|
|
22
|
+
};
|
package/ssr/root.jsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { LiveReload } from "@ssr/liveReload.jsx";
|
|
2
2
|
import { ViteScripts } from "@ssr/viteScripts.jsx";
|
|
3
|
-
import { Outlet } from "react-
|
|
3
|
+
import { Outlet as OutletSlot } from "react-slotx";
|
|
4
|
+
import { Outlet as OutletRoutes } from "react-router";
|
|
4
5
|
|
|
5
6
|
export const RootDocument = () => {
|
|
6
7
|
return (
|
|
@@ -8,12 +9,11 @@ export const RootDocument = () => {
|
|
|
8
9
|
<head>
|
|
9
10
|
<meta charSet="utf-8" />
|
|
10
11
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
11
|
-
<title>Vite + React SSR</title>
|
|
12
|
-
<link rel="icon" href="vite.svg" type="image/svg" />
|
|
13
12
|
<LiveReload />
|
|
13
|
+
<OutletSlot name="head"/>
|
|
14
14
|
</head>
|
|
15
15
|
<body>
|
|
16
|
-
<
|
|
16
|
+
<OutletRoutes />
|
|
17
17
|
<ViteScripts />
|
|
18
18
|
</body>
|
|
19
19
|
</html>
|