@canonical/react-ssr 0.11.0 → 0.12.0

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.
Files changed (2) hide show
  1. package/README.md +120 -92
  2. package/package.json +6 -6
package/README.md CHANGED
@@ -1,86 +1,145 @@
1
- # Canonical React SSR: No-Hassle Server-Side Rendering for React
1
+ # @canonical/react-ssr
2
2
 
3
- This guide demonstrates how to set up server-side rendering (SSR) for React applications using `@canonical/react-ssr`. It covers everything from installation to building and handling SSR requests with client and server entry points.
3
+ Server-side rendering utilities for React applications. Provides streaming HTML rendering with `JSXRenderer`, Express middleware with `serveStream`, and automatic script/link tag injection from your build output.
4
4
 
5
- ## Table of Contents
6
- 1. [Installation](#installation)
7
- 2. [Quick Start](#quick-start)
8
- - [Server-Side Entry Point](#server-side-entry-point)
9
- - [Client-Side Entry Point](#client-side-entry-point)
10
- - [Building Your Application](#building-your-application)
11
- - [Server Request Handling](#server-request-handling)
12
- - [Injecting the Client Application](#injecting-the-client-application)
13
- 3. [Customization](#customization)
14
- - [Bootstrap Scripts](#bootstrap-scripts)
5
+ ## Installation
6
+
7
+ ```bash
8
+ bun add @canonical/react-ssr
9
+ ```
15
10
 
16
- ---
11
+ Peer dependencies: `react`, `react-dom`, `express` (for Express usage).
17
12
 
18
- ## Installation
13
+ ## Express Server
14
+
15
+ Create a renderer that wraps your server entry component:
16
+
17
+ ```tsx
18
+ // src/ssr/renderer.tsx
19
+ import { JSXRenderer } from "@canonical/react-ssr/renderer";
20
+ import htmlString from "../../dist/client/index.html?raw";
21
+ import EntryServer from "./entry-server.js";
22
+
23
+ const Renderer = new JSXRenderer(EntryServer, { htmlString });
24
+ export default Renderer.render;
25
+ ```
19
26
 
20
- First, install the `@canonical/react-ssr` package:
27
+ Create an Express server using `serveStream`:
28
+
29
+ ```ts
30
+ // src/ssr/server.ts
31
+ import { serveStream } from "@canonical/react-ssr/server";
32
+ import express from "express";
33
+ import render from "./renderer.js";
34
+
35
+ const app = express();
36
+ app.use("/assets", express.static("dist/client/assets"));
37
+ app.use(serveStream(render));
38
+ app.listen(5173);
39
+ ```
40
+
41
+ Build and run:
21
42
 
22
43
  ```bash
23
- npm install @canonical/react-ssr
44
+ vite build --ssrManifest --outDir dist/client
45
+ vite build --ssr src/ssr/server.ts --outDir dist/server
46
+ node dist/server/server.js
24
47
  ```
25
48
 
26
- ## Quick Start
49
+ ## Bun Server
50
+
51
+ The renderer works the same way. For Bun's native server, convert the pipeable stream:
52
+
53
+ ```ts
54
+ // src/ssr/server-bun.ts
55
+ import render from "./renderer.js";
56
+ import { Readable } from "node:stream";
57
+
58
+ Bun.serve({
59
+ port: 5173,
60
+ async fetch(req) {
61
+ const url = new URL(req.url);
62
+
63
+ // Serve static assets
64
+ if (url.pathname.startsWith("/assets")) {
65
+ return new Response(Bun.file(`dist/client${url.pathname}`));
66
+ }
67
+
68
+ // SSR render
69
+ const { pipe } = render(req, null);
70
+ const readable = Readable.toWeb(Readable.from(pipeToIterable(pipe)));
71
+ return new Response(readable, {
72
+ headers: { "Content-Type": "text/html; charset=utf-8" },
73
+ });
74
+ },
75
+ });
76
+
77
+ function pipeToIterable(pipe: (dest: NodeJS.WritableStream) => void) {
78
+ const { Readable } = require("node:stream");
79
+ const passthrough = new (require("node:stream").PassThrough)();
80
+ pipe(passthrough);
81
+ return passthrough;
82
+ }
83
+ ```
27
84
 
28
- This section walks you through setting up SSR for your React app, including creating entry points, building your app, and handling SSR requests.
85
+ Or use Express compatibility mode with Bun:
29
86
 
30
- ### Entrypoints
87
+ ```ts
88
+ // Bun can run Express directly
89
+ import app from "./server.js"; // Your Express server
90
+ export default app;
91
+ ```
31
92
 
32
- You will notice that this setup encourages two entry points: one for the server, and one for the client.
33
- The server entry point includes the full application HTML for compatibility with streams.
34
- The client entry point includes just the application component, which is hydrated on the client.
93
+ ## Entry Points
35
94
 
36
- ### Server-Side Entry Point
95
+ ### Server Entry
37
96
 
38
- Create a server-side entry point to wrap your application and inject the necessary scripts and links into the HTML.
97
+ The server entry renders the full HTML document. `scriptTags` and `linkTags` are extracted from your build output and injected automatically:
39
98
 
40
99
  ```tsx
41
100
  // src/ssr/entry-server.tsx
101
+ import type { ReactServerEntrypointComponent, RendererServerEntrypointProps } from "@canonical/react-ssr/renderer";
42
102
  import Application from "../Application.js";
43
- import React from "react";
44
- import type {ReactServerEntrypointComponent, RendererServerEntrypointProps} from "@canonical/react-ssr/renderer";
45
103
 
46
- // Define your server-side entry point component
47
- const EntryServer: ReactServerEntrypointComponent<RendererServerEntrypointProps> = ({ lang = "en", scriptTags, linkTags }) => (
104
+ const EntryServer: ReactServerEntrypointComponent<RendererServerEntrypointProps> = ({
105
+ lang = "en",
106
+ scriptTags,
107
+ linkTags,
108
+ }) => (
48
109
  <html lang={lang}>
49
110
  <head>
50
- <title>Canonical React SSR</title>
51
- {scriptTags}
111
+ <title>My App</title>
52
112
  {linkTags}
53
113
  </head>
54
114
  <body>
55
115
  <div id="root">
56
116
  <Application />
57
117
  </div>
118
+ {scriptTags}
58
119
  </body>
59
120
  </html>
60
121
  );
61
122
 
62
123
  export default EntryServer;
63
124
  ```
64
- This component is responsible for rendering the HTML structure and injecting the necessary script and link tags that are required for hydration on the client.
65
125
 
66
- ### Client-Side Entry Point
67
- Set up client-side hydration to rehydrate your app after the SSR content has been rendered.
126
+ ### Client Entry
127
+
128
+ The client entry hydrates the server-rendered HTML:
129
+
68
130
  ```tsx
69
131
  // src/ssr/entry-client.tsx
70
132
  import { hydrateRoot } from "react-dom/client";
71
133
  import Application from "../Application.js";
72
134
 
73
- // Hydrate the client-side React app after the server-rendered HTML is loaded
74
- hydrateRoot(document.getElementById("root") as HTMLElement, <Application />);
135
+ hydrateRoot(document.getElementById("root")!, <Application />);
75
136
  ```
76
137
 
77
- ### Building your application
78
- To build your SSR React app, use a tool like Vite, Webpack, or Next.
79
- The build process should include both client and server bundles. First, build the client-side app, then the server-side entry point.
80
- The example below uses Vite.
138
+ ## Building
139
+
140
+ Two-phase build with Vite:
81
141
 
82
- ```json5
83
- // package.json
142
+ ```json
84
143
  {
85
144
  "scripts": {
86
145
  "build": "bun run build:client && bun run build:server",
@@ -88,65 +147,34 @@ The example below uses Vite.
88
147
  "build:server": "vite build --ssr src/ssr/server.ts --outDir dist/server"
89
148
  }
90
149
  }
91
-
92
150
  ```
93
151
 
94
- ### Server Request Handling
95
-
96
- Once your app is built, you can set up an Express server to handle SSR requests.
97
- See [this file](../../../apps/react/boilerplate-vite/src/ssr/server.ts) as an example.
98
-
99
- ### Injecting the Client Application
152
+ The client build produces `dist/client/index.html` with bundled script/link tags. The server build imports this HTML string to extract those tags for injection.
100
153
 
101
- Your client-side entry point must be executed by the client upon page load to rehydrate the server-rendered app.
154
+ ## Customization
102
155
 
103
- Example for injecting the client application into your HTML with Vite:
104
-
105
- ```html
106
- <html lang="en">
107
- <head>
108
- <meta charset="UTF-8" />
109
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
110
- <title>Vite + React + TS</title>
111
- </head>
112
- <body>
113
- <div id="root"></div>
114
- <!-- Inject the client-side entry point -->
115
- <script type="module" src="/src/ssr/entry-client.tsx"></script>
116
- </body>
117
- </html>
118
- ```
119
- This script will hydrate the app on the client, connecting the React app to the server-rendered HTML.
120
-
121
- #### Customization
122
- You can inject additional bootstrapping scripts to customize the client-side setup.
123
- This is useful if you need more control over how the client app boots.
124
-
125
- ##### Bootstrap Scripts
126
- You can pass custom modules, scripts, or inline script content to be executed on the client before the app is hydrated.
127
-
128
- ###### Options
129
- - `bootstrapModules`: An array of module paths. Generates `<script type="module" src="{SCRIPT_LINK}"></script>` elements.
130
- - `bootstrapScripts`: An array of script paths. Generates `<script type="text/javascript" src="{SCRIPT_LINK}"></script>` elements.
131
- - `bootstrapScriptContent`: Raw script content. Generates `<script type="text/javascript">{SCRIPT_CONTENT}</script>` elements.
156
+ Pass options to React's `renderToPipeableStream`:
132
157
 
133
158
  ```ts
134
- import { JSXRenderer } from "@canonical/react-ssr/renderer";
135
- // Pass custom bootstrap scripts to the renderer
136
- const Renderer = new JSXRenderer(
137
- EntryServer,
138
- {
139
- htmlString,
140
- renderToPipeableStreamOptions: {
141
- bootstrapModules: ["src/ssr/entry-client.tsx"] // Adds the client-side entry module to the page
142
- }
143
- }
144
- );
159
+ const Renderer = new JSXRenderer(EntryServer, {
160
+ htmlString,
161
+ renderToPipeableStreamOptions: {
162
+ bootstrapModules: ["src/ssr/entry-client.tsx"],
163
+ onShellReady() { console.log("Shell ready"); },
164
+ onError(err) { console.error(err); },
165
+ },
166
+ });
145
167
  ```
146
168
 
147
- The `JSXRenderer` also accepts `renderToPipeableStreamOptions`, which are passed to react-dom/server`'s `renderToPipeableStream()`.
148
-
149
- For further information, refer to [React's `renderToPipeableStream()` documentation](https://react.dev/reference/react-dom/server/renderToPipeableStream#parameters).
169
+ Options include:
170
+ - `bootstrapModules` - ES modules to load (`<script type="module">`)
171
+ - `bootstrapScripts` - Scripts to load (`<script>`)
172
+ - `bootstrapScriptContent` - Inline script content
150
173
 
174
+ See [React's renderToPipeableStream documentation](https://react.dev/reference/react-dom/server/renderToPipeableStream) for all options.
151
175
 
176
+ ## Examples
152
177
 
178
+ See working examples in the monorepo:
179
+ - [`apps/react/boilerplate-vite`](../../../apps/react/boilerplate-vite) - Vite + Express
180
+ - [`apps/react/demo`](../../../apps/react/demo) - Full application example
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@canonical/react-ssr",
3
3
  "description": "TBD",
4
- "version": "0.11.0",
4
+ "version": "0.12.0",
5
5
  "type": "module",
6
6
  "module": "dist/esm/index.js",
7
7
  "types": "dist/types/index.d.ts",
@@ -51,20 +51,20 @@
51
51
  },
52
52
  "devDependencies": {
53
53
  "@biomejs/biome": "2.3.11",
54
- "@canonical/biome-config": "^0.11.0",
55
- "@canonical/typescript-config-base": "^0.11.0",
56
- "@canonical/webarchitect": "^0.11.0",
54
+ "@canonical/biome-config": "^0.12.0",
55
+ "@canonical/typescript-config-base": "^0.12.0",
56
+ "@canonical/webarchitect": "^0.12.0",
57
57
  "@types/express": "^5.0.6",
58
58
  "@types/react": "^19.2.8",
59
59
  "@types/react-dom": "^19.2.3",
60
60
  "typescript": "^5.9.3"
61
61
  },
62
62
  "dependencies": {
63
- "@canonical/utils": "^0.11.0",
63
+ "@canonical/utils": "^0.12.0",
64
64
  "domhandler": "^5.0.3",
65
65
  "express": "^5.2.1",
66
66
  "htmlparser2": "^10.0.0",
67
67
  "react-dom": "^19.2.3"
68
68
  },
69
- "gitHead": "29125736059d1880b3d0fc277b218a63a2574f28"
69
+ "gitHead": "0b491caff8f797fef4ba4b7f5514a7c5b915a481"
70
70
  }