vite-plugin-ssr-config 1.0.4 → 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 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"), and [React Query](https://tanstack.com/query "react-query - Data fetching and caching library").
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
@@ -44,7 +46,7 @@ export default defineConfig({
44
46
  plugins: [
45
47
  react(),
46
48
  web({
47
- moduleFile: '.ssr/routes.tsx'
49
+ moduleId: 'ssr-pages'
48
50
  }),
49
51
  ssr({
50
52
  chacheDir: '.ssr'
@@ -53,6 +55,32 @@ export default defineConfig({
53
55
  });
54
56
  ```
55
57
 
58
+ ### Legacy project
59
+
60
+ If you use the vite-plugin-pages, you need config the moduleId
61
+
62
+ ```typescript
63
+ import { defineConfig } from "vite";
64
+ import react from "@vitejs/plugin-react";
65
+ import pages from "vite-plugin-pages";
66
+ import ssr from "vite-plugin-ssr-config";
67
+
68
+ export default defineConfig({
69
+ plugins: [
70
+ react(),
71
+ pages({
72
+ moduleId: "ssr-pages",
73
+ routeStyle: "remix",
74
+ dirs: "src/pages",
75
+ }),
76
+ ssr({
77
+ chacheDir: '.ssr'
78
+ })
79
+ ],
80
+ });
81
+ ```
82
+
83
+
56
84
  ## Default Configuration
57
85
 
58
86
  The following default values are provided for each configurable attribute in the plugin:
@@ -240,3 +268,84 @@ These custom commands are designed to provide flexibility in your Vite SSR workf
240
268
  ## Use Case
241
269
 
242
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
- return async () => {
435
- devServer.middlewares.use(async (req, res, next) => {
436
- const indexHtmlPath = import_path4.default.join(root, req.url, "index.html");
437
- if (import_fs_extra2.default.existsSync(indexHtmlPath)) {
438
- return devServer.transformIndexHtml(
439
- req.url,
440
- import_fs_extra2.default.readFileSync(indexHtmlPath, "utf-8")
441
- ).then((html) => {
442
- res.setHeader("Content-Type", "text/html");
443
- res.setHeader("Pragma", "no-cache");
444
- res.setHeader("Expires", "0");
445
- res.end(html);
446
- });
447
- }
448
- next();
449
- });
450
- devServer.middlewares.use(async (req, res, next) => {
451
- try {
452
- process.env.SSR = true;
453
- const mod = await devServer.ssrLoadModule("@ssr/handler.js", {
454
- fixStacktrace: true
455
- });
456
- await mod.handler(req, res, next);
457
- } catch (error) {
458
- next(error);
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
@@ -2,6 +2,7 @@ import { UserConfig, PluginOption } from 'vite';
2
2
 
3
3
  type SSRConfig = {
4
4
  root: string;
5
+ version: "1.0" | "1.1";
5
6
  entryClient: string;
6
7
  entryRender: string;
7
8
  server: string;
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ import { UserConfig, PluginOption } from 'vite';
2
2
 
3
3
  type SSRConfig = {
4
4
  root: string;
5
+ version: "1.0" | "1.1";
5
6
  entryClient: string;
6
7
  entryRender: string;
7
8
  server: string;
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
- return async () => {
398
- devServer.middlewares.use(async (req, res, next) => {
399
- const indexHtmlPath = path4.join(root, req.url, "index.html");
400
- if (fs2.existsSync(indexHtmlPath)) {
401
- return devServer.transformIndexHtml(
402
- req.url,
403
- fs2.readFileSync(indexHtmlPath, "utf-8")
404
- ).then((html) => {
405
- res.setHeader("Content-Type", "text/html");
406
- res.setHeader("Pragma", "no-cache");
407
- res.setHeader("Expires", "0");
408
- res.end(html);
409
- });
410
- }
411
- next();
412
- });
413
- devServer.middlewares.use(async (req, res, next) => {
414
- try {
415
- process.env.SSR = true;
416
- const mod = await devServer.ssrLoadModule("@ssr/handler.js", {
417
- fixStacktrace: true
418
- });
419
- await mod.handler(req, res, next);
420
- } catch (error) {
421
- next(error);
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.4",
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
- "dotenv-local": "^1.0.2",
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.0",
56
+ "@types/express": "^5.0.6",
54
57
  "@types/fs-extra": "^11.0.4",
55
- "@types/node": "^22.10.2",
56
- "express": "^4.21.2",
57
- "tsup": "^8.3.5",
58
- "typescript": "^5.7.2",
59
- "vite": "^4.0.0"
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
  }
@@ -1,18 +1,27 @@
1
- import { PageBrowser } from "@ssr/pageBrowser.jsx";
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
- const setHydratedState = () => {
8
- throw Error("Changes Not Allowed");
9
- };
17
+ hydrate(queryClient, hydratedState);
10
18
  hydrateRoot(
11
19
  document,
12
20
  <StrictMode>
13
21
  <PageBrowser
14
- hydratedState={hydratedState}
15
- setHydratedState={setHydratedState}
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
  });
@@ -1,28 +1,26 @@
1
- import { PageServer } from "@ssr/pageServer.jsx";
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
- const createRequestContext = () => {
7
- return {
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 context = createRequestContext();
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
- path={request.originalUrl}
24
- hydratedState={""} // Access is Not Allowed
25
- setHydratedState={context.setHydratedState.bind(context)}
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) + stateScript + str.slice(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
- try {
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
- //Force Fix
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
+ };
@@ -1,34 +1,20 @@
1
1
  import { ErrorBoundary } from "@ssr/errorBoundary.jsx";
2
2
  import { RootRoutes } from "@ssr/rootRoutes.jsx";
3
- import { StrictMode, Suspense } from "react";
4
- import { QueryClient, QueryClientProvider, hydrate } from "react-query";
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 queryClient = new QueryClient({
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
- <BrowserRouter
22
- basename={process.env.SSR_BASENAME}
23
- future={{ v7_startTransition: true, v7_relativeSplatPath: true }}
24
- >
25
- <StrictMode>
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>
@@ -1,39 +1,22 @@
1
- import { ErrorBoundary } from "@ssr/errorBoundary.jsx";
2
- import { RootRoutes } from "@ssr/rootRoutes.jsx";
3
- import { StrictMode, Suspense } from "react";
4
- import { dehydrate, QueryClient, QueryClientProvider } from "react-query";
5
- import { StaticRouter } from "react-router";
6
-
7
- export const PageServer = ({ path, hydratedState, setHydratedState }) => {
8
- const queryServer = new QueryClient({
9
- defaultOptions: {
10
- queries: {
11
- suspense: true,
12
- },
13
- },
14
- });
15
- queryServer.getQueryCache().subscribe(() => {
16
- const state = dehydrate(queryServer);
17
- setHydratedState(state);
18
- });
19
- return (
20
- <ErrorBoundary suppressHydrationWarning={true}>
21
- <QueryClientProvider client={queryServer}>
22
- <Suspense>
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-router";
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
- <Outlet />
16
+ <OutletRoutes />
17
17
  <ViteScripts />
18
18
  </body>
19
19
  </html>
@@ -1,7 +1,7 @@
1
1
  import { RootDocument } from "@ssr/root.jsx";
2
2
  import React from "react";
3
3
  import { useRoutes } from "react-router";
4
- import routes from "./routes";
4
+ import routes from "ssr-pages";
5
5
 
6
6
  export const RootRoutes = (props) => {
7
7
  const newRoutes = [