react_on_rails_pro 16.2.0.beta.20 → 16.2.0.rc.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +3 -1
- data/CONTRIBUTING.md +64 -43
- data/Gemfile.lock +4 -4
- data/README.md +1 -1
- data/docs/bundle-caching.md +22 -8
- data/docs/caching.md +39 -27
- data/docs/code-splitting-loadable-components.md +2 -2
- data/docs/code-splitting.md +74 -70
- data/docs/configuration.md +6 -6
- data/docs/contributors-info/onboarding-customers.md +2 -1
- data/docs/contributors-info/releasing.md +1 -0
- data/docs/contributors-info/style.md +23 -15
- data/docs/home-pro.md +33 -15
- data/docs/installation.md +51 -2
- data/docs/js-memory-leaks.md +2 -3
- data/docs/node-renderer/debugging.md +5 -1
- data/docs/node-renderer/error-reporting-and-tracing.md +27 -15
- data/docs/node-renderer/heroku.md +4 -5
- data/docs/profiling-server-side-rendering-code.md +43 -42
- data/docs/react-server-components/add-streaming-and-interactivity.md +1 -1
- data/docs/react-server-components/create-without-ssr.md +18 -18
- data/docs/react-server-components/glossary.md +22 -3
- data/docs/react-server-components/how-react-server-components-work.md +25 -18
- data/docs/react-server-components/inside-client-components.md +19 -18
- data/docs/react-server-components/purpose-and-benefits.md +24 -14
- data/docs/react-server-components/rendering-flow.md +7 -3
- data/docs/react-server-components/server-side-rendering.md +23 -22
- data/docs/release-notes/4.0.md +103 -94
- data/docs/release-notes/v4-react-server-components.md +16 -16
- data/docs/streaming-server-rendering.md +2 -4
- data/docs/troubleshooting.md +5 -2
- data/docs/updating.md +49 -13
- data/lib/react_on_rails_pro/request.rb +18 -3
- data/lib/react_on_rails_pro/version.rb +1 -1
- data/rakelib/dummy_apps.rake +4 -4
- data/rakelib/lint.rake +1 -1
- data/rakelib/run_rspec.rake +3 -3
- metadata +4 -4
|
@@ -40,12 +40,12 @@ end
|
|
|
40
40
|
> After enabling RSC support, you must add the `'use client';` directive at the top of your JavaScript entry points (packs) that are not yet migrated to support Server Components.
|
|
41
41
|
>
|
|
42
42
|
> This directive tells React that these files should be treated as client components. You don't need to add this directive to all JavaScript files - only the entry points. Any file imported by a file marked with `'use client';` will automatically be treated as a client component as well. Without this directive, React will assume these files contain Server Components, which will cause errors if the components use client-side features like:
|
|
43
|
+
>
|
|
43
44
|
> - `useState` or other state hooks
|
|
44
45
|
> - `useEffect` or other effect hooks
|
|
45
46
|
> - Event handlers (onClick, onChange, etc.)
|
|
46
47
|
> - Browser APIs
|
|
47
48
|
|
|
48
|
-
|
|
49
49
|
For example:
|
|
50
50
|
|
|
51
51
|
```js
|
|
@@ -55,7 +55,6 @@ For example:
|
|
|
55
55
|
// ... existing code ...
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
|
|
59
58
|
3. Create a new Webpack configuration to generate React Server Components bundles (RSC bundles) (usually named `rsc-bundle.js`).
|
|
60
59
|
|
|
61
60
|
RSC bundle is a clone of the server bundle `server-bundle.js` but we just add the RSC loader `react-on-rails-rsc/WebpackLoader` to the used loaders.
|
|
@@ -63,6 +62,7 @@ RSC bundle is a clone of the server bundle `server-bundle.js` but we just add th
|
|
|
63
62
|
You can check the [How React Server Components work](how-react-server-components-work.md) for more information about the RSC loader (It's better to read it after reading this article).
|
|
64
63
|
|
|
65
64
|
Create a new file `config/webpack/rscWebpackConfig.js`:
|
|
65
|
+
|
|
66
66
|
```js
|
|
67
67
|
// use the same config as serverWebpackConfig.js but add the RSC loader
|
|
68
68
|
const serverWebpackConfig = require('./serverWebpackConfig');
|
|
@@ -95,7 +95,7 @@ const configureRsc = () => {
|
|
|
95
95
|
const rules = rscConfig.module.rules;
|
|
96
96
|
rules.forEach((rule) => {
|
|
97
97
|
if (Array.isArray(rule.use)) {
|
|
98
|
-
// Ensure this loader runs before the JS loader (Babel loader in this case) to properly exclude client components from the RSC bundle.
|
|
98
|
+
// Ensure this loader runs before the JS loader (Babel loader in this case) to properly exclude client components from the RSC bundle.
|
|
99
99
|
// If your project uses a different JS loader, insert it before that loader instead.
|
|
100
100
|
const babelLoader = extractLoader(rule, 'babel-loader');
|
|
101
101
|
if (babelLoader) {
|
|
@@ -240,13 +240,13 @@ async function ReactServerComponent() {
|
|
|
240
240
|
uptime: Math.floor(os.uptime() / 3600), // Convert to hours
|
|
241
241
|
totalMemory: Math.floor(os.totalmem() / (1024 * 1024 * 1024)), // Convert to GB
|
|
242
242
|
freeMemory: Math.floor(os.freemem() / (1024 * 1024 * 1024)), // Convert to GB
|
|
243
|
-
cpus: os.cpus().length
|
|
243
|
+
cpus: os.cpus().length,
|
|
244
244
|
};
|
|
245
245
|
|
|
246
246
|
return (
|
|
247
247
|
<div className="server-component-demo">
|
|
248
248
|
<h2>React Server Component Demo</h2>
|
|
249
|
-
|
|
249
|
+
|
|
250
250
|
<section>
|
|
251
251
|
<h3>Date Calculations (using moment.js)</h3>
|
|
252
252
|
<p>Date Range: {formattedDateRange}</p>
|
|
@@ -277,16 +277,17 @@ async function ReactServerComponent() {
|
|
|
277
277
|
</section>
|
|
278
278
|
|
|
279
279
|
<div className="note">
|
|
280
|
-
<p
|
|
281
|
-
|
|
282
|
-
|
|
280
|
+
<p>
|
|
281
|
+
<strong>Note:</strong> The heavy libraries (moment.js, lodash) and Node.js modules (os) used in this
|
|
282
|
+
component stay on the server and are not shipped to the client, reducing the client bundle size
|
|
283
|
+
significantly.
|
|
284
|
+
</p>
|
|
283
285
|
</div>
|
|
284
286
|
</div>
|
|
285
287
|
);
|
|
286
288
|
}
|
|
287
289
|
|
|
288
290
|
export default ReactServerComponent;
|
|
289
|
-
|
|
290
291
|
```
|
|
291
292
|
|
|
292
293
|
## Create a React Server Component Page
|
|
@@ -318,7 +319,7 @@ If you didn't enable `auto_load_bundle`, you need to register the React Server C
|
|
|
318
319
|
|
|
319
320
|
```js
|
|
320
321
|
// client/app/packs/server-bundle.js
|
|
321
|
-
import registerServerComponent from 'react-on-rails/registerServerComponent/server';
|
|
322
|
+
import registerServerComponent from 'react-on-rails-pro/registerServerComponent/server';
|
|
322
323
|
import ReactServerComponentPage from './components/ReactServerComponentPage';
|
|
323
324
|
|
|
324
325
|
registerServerComponent({
|
|
@@ -328,22 +329,22 @@ registerServerComponent({
|
|
|
328
329
|
|
|
329
330
|
```js
|
|
330
331
|
// client/app/packs/client-bundle.js
|
|
331
|
-
import registerServerComponent from 'react-on-rails/registerServerComponent/client';
|
|
332
|
+
import registerServerComponent from 'react-on-rails-pro/registerServerComponent/client';
|
|
332
333
|
|
|
333
|
-
registerServerComponent(
|
|
334
|
-
{ rscPayloadGenerationUrlPath: 'rsc_payload/' },
|
|
335
|
-
'ReactServerComponentPage',
|
|
336
|
-
);
|
|
334
|
+
registerServerComponent({ rscPayloadGenerationUrlPath: 'rsc_payload/' }, 'ReactServerComponentPage');
|
|
337
335
|
```
|
|
338
336
|
|
|
339
337
|
As you can see, server components are not registered using the `ReactOnRails.register` function. Instead, we use the `registerServerComponent` function to register the server component. Also, `registerServerComponent` has different options for the client bundle and the server bundle.
|
|
338
|
+
|
|
340
339
|
- For the server bundle, the component itself is passed to the `registerServerComponent` function, so the component is bundled into the server bundle.
|
|
341
340
|
- For the client bundle, we pass the component name as an argument to the `registerServerComponent` function, so the component is not bundled into the client bundle.
|
|
342
341
|
|
|
343
342
|
As you can see at [How React Server Components work](how-react-server-components-work.md):
|
|
343
|
+
|
|
344
344
|
- Server components are rendered on the client using the rsc payload not the component itself.
|
|
345
345
|
|
|
346
346
|
And as you can see at [React Server Components Rendering Flow](./rendering-flow.md):
|
|
347
|
+
|
|
347
348
|
- In the future, the server bundle will use the RSC payload to render the server component on the server side as well.
|
|
348
349
|
|
|
349
350
|
The `rscPayloadGenerationUrlPath` option will be explained in detail later in this document. For now, just know that it specifies the base URL path for React Server Component requests.
|
|
@@ -361,7 +362,6 @@ end
|
|
|
361
362
|
|
|
362
363
|
This will add the `/rsc_payload` path to the routes. This is the base URL path that will receive requests from the client to render the React Server Components. `rsc_payload_route` is explained in the [How React Server Components work](how-react-server-components-work.md) document.
|
|
363
364
|
|
|
364
|
-
|
|
365
365
|
## Add Route to the React Server Component Page
|
|
366
366
|
|
|
367
367
|
Add the following route to the `config/routes.rb` file:
|
|
@@ -380,7 +380,7 @@ This route will be used to render the React Server Component Page.
|
|
|
380
380
|
Create a new file `app/views/pages/react_server_component_without_ssr.html.erb`:
|
|
381
381
|
|
|
382
382
|
```erb
|
|
383
|
-
<%= react_component("ReactServerComponentPage",
|
|
383
|
+
<%= react_component("ReactServerComponentPage",
|
|
384
384
|
prerender: false,
|
|
385
385
|
trace: true,
|
|
386
386
|
id: "ReactServerComponentPage-react-component-0") %>
|
|
@@ -417,6 +417,7 @@ Also, by looking at the console, we can see the log
|
|
|
417
417
|
```
|
|
418
418
|
[SERVER] Hello from ReactServerComponent
|
|
419
419
|
```
|
|
420
|
+
|
|
420
421
|
The `[SERVER]` prefix indicates that the component was executed on the server side. The absence of any client-side logs confirms that no client-side rendering or hydration occurred. This demonstrates a key characteristic of React Server Components - they run exclusively on the server without requiring any JavaScript execution in the browser, leading to improved performance and reduced client-side bundle sizes.
|
|
421
422
|
|
|
422
423
|
## How the React Server Component Page is Rendered on Browser?
|
|
@@ -445,4 +446,3 @@ The RSC payload format and how React processes it is explained in detail in the
|
|
|
445
446
|
## Next Steps
|
|
446
447
|
|
|
447
448
|
Now that you understand the basics of React Server Components, you can proceed to the next article: [Add Streaming and Interactivity to RSC Page](./add-streaming-and-interactivity.md) to learn how to enhance your RSC page with streaming capabilities and client-side interactivity.
|
|
448
|
-
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# React Server Components Glossary
|
|
2
2
|
|
|
3
3
|
### RSC (React Server Component)
|
|
4
|
+
|
|
4
5
|
A React architecture that allows components to execute exclusively on the server while streaming results to the client. Benefits include:
|
|
6
|
+
|
|
5
7
|
- Reduced client-side JavaScript
|
|
6
8
|
- Direct access to server resources
|
|
7
9
|
- Improved initial page load
|
|
@@ -10,18 +12,21 @@ A React architecture that allows components to execute exclusively on the server
|
|
|
10
12
|
## Types of Components
|
|
11
13
|
|
|
12
14
|
### Server Components
|
|
15
|
+
|
|
13
16
|
Components that run exclusively on the server (not included in the client bundle). They can:
|
|
17
|
+
|
|
14
18
|
- Directly access server-side resources (databases, filesystems)
|
|
15
19
|
- Keep dependencies server-side
|
|
16
20
|
- Perform async operations
|
|
17
21
|
- Cannot contain state or browser-only APIs
|
|
18
22
|
|
|
19
23
|
For example:
|
|
24
|
+
|
|
20
25
|
```jsx
|
|
21
|
-
import fetch from
|
|
26
|
+
import fetch from 'node-fetch';
|
|
22
27
|
|
|
23
28
|
async function ServerComponent() {
|
|
24
|
-
const data = await (await fetch(
|
|
29
|
+
const data = await (await fetch('https://jsonplaceholder.org/posts/1')).json();
|
|
25
30
|
const databaseData = await getDatabaseData();
|
|
26
31
|
return (
|
|
27
32
|
<div>
|
|
@@ -33,11 +38,12 @@ async function ServerComponent() {
|
|
|
33
38
|
}
|
|
34
39
|
```
|
|
35
40
|
|
|
36
|
-
|
|
37
41
|
### Client Components
|
|
42
|
+
|
|
38
43
|
Components marked with `'use client'` directive that run on client. They can contain state, effects, and event handlers. These components get hydrated in the browser.
|
|
39
44
|
|
|
40
45
|
For example:
|
|
46
|
+
|
|
41
47
|
```jsx
|
|
42
48
|
function ClientComponent() {
|
|
43
49
|
const [count, setCount] = useState(0);
|
|
@@ -53,6 +59,7 @@ function ClientComponent() {
|
|
|
53
59
|
Note: Server components can import client components, but client components cannot import server components. However, server components can be passed as props to client components.
|
|
54
60
|
|
|
55
61
|
For example:
|
|
62
|
+
|
|
56
63
|
```jsx
|
|
57
64
|
function ParentServerComponent() {
|
|
58
65
|
return <ClientComponent serverComponent={<ServerComponent />} />;
|
|
@@ -62,33 +69,43 @@ function ParentServerComponent() {
|
|
|
62
69
|
## Bundle Related
|
|
63
70
|
|
|
64
71
|
### React Server Components Bundle (RSC Bundle) (usually `rsc-bundle.js`)
|
|
72
|
+
|
|
65
73
|
A new server-side bundle introduced by React Server Components. It contains server components and their dependencies only. It doesn't include client components. It should be the same as the `server_bundle.js` bundle. But it uses the `react-on-rails-rsc/WebpackLoader` loader to trim the client components from the bundle.
|
|
66
74
|
|
|
67
75
|
### Client Bundle
|
|
76
|
+
|
|
68
77
|
The JavaScript bundle that runs in the browser, containing client components and their dependencies. This bundle is responsible for hydration and client-side interactivity.
|
|
69
78
|
|
|
70
79
|
## Concepts
|
|
71
80
|
|
|
72
81
|
### Flight Format (RSC Format)
|
|
82
|
+
|
|
73
83
|
The wire format used by React Server Components to stream component data from server to client. It's a compact binary format that represents the component tree and its data.
|
|
74
84
|
|
|
75
85
|
### Hydration
|
|
86
|
+
|
|
76
87
|
The process where React attaches event handlers and state to server-rendered HTML in the browser. With RSC, hydration happens selectively only for Client Components.
|
|
77
88
|
|
|
78
89
|
### RSC Payload (Flight Payload)
|
|
90
|
+
|
|
79
91
|
The serialized output of server components that gets streamed to the client. Contains:
|
|
92
|
+
|
|
80
93
|
- React render tree of the server component
|
|
81
94
|
- References to client components that need hydration
|
|
82
95
|
- Data for client components
|
|
83
96
|
|
|
84
97
|
### Selective Hydration
|
|
98
|
+
|
|
85
99
|
A feature where client components can hydrate independently and in parallel, allowing for:
|
|
100
|
+
|
|
86
101
|
- Progressive interactivity
|
|
87
102
|
- Prioritized hydration of visible components
|
|
88
103
|
- Better performance on slower devices
|
|
89
104
|
|
|
90
105
|
### Streaming
|
|
106
|
+
|
|
91
107
|
The ability to progressively send server component renders to the client before all data is ready. Benefits include:
|
|
108
|
+
|
|
92
109
|
- Faster Time to First Byte (TTFB)
|
|
93
110
|
- Progressive rendering of content
|
|
94
111
|
- Better user experience during slow data fetches
|
|
@@ -96,7 +113,9 @@ The ability to progressively send server component renders to the client before
|
|
|
96
113
|
## Technical
|
|
97
114
|
|
|
98
115
|
### Client Component Manifest
|
|
116
|
+
|
|
99
117
|
A JSON file mapping component paths to their corresponding JavaScript chunks. Used by RSC to determine which client-side code to load for hydration.
|
|
100
118
|
|
|
101
119
|
### RSC URL Path
|
|
120
|
+
|
|
102
121
|
The endpoint path where RSC requests are handled, defaulting to "rsc_payload/" in the React on Rails Pro configuration.
|
|
@@ -30,7 +30,7 @@ export default function HomePage() {
|
|
|
30
30
|
<Footer />
|
|
31
31
|
</div>
|
|
32
32
|
);
|
|
33
|
-
}
|
|
33
|
+
}
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
It replaces all exports of the file with the client references.
|
|
@@ -38,26 +38,27 @@ It replaces all exports of the file with the client references.
|
|
|
38
38
|
> [!NOTE]
|
|
39
39
|
> The code shown below represents internal implementation details of how React Server Components work under the hood. You don't need to understand these details to use React Server Components effectively in your application. This section is included for those interested in the technical implementation.
|
|
40
40
|
|
|
41
|
-
|
|
42
41
|
```js
|
|
43
|
-
import { registerClientReference } from
|
|
42
|
+
import { registerClientReference } from 'react-server-dom-webpack/server';
|
|
44
43
|
|
|
45
44
|
export const Header = registerClientReference(
|
|
46
45
|
function () {
|
|
47
46
|
throw new Error(
|
|
48
|
-
"Attempted to call Header() from the server but Header is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component."
|
|
47
|
+
"Attempted to call Header() from the server but Header is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.",
|
|
49
48
|
);
|
|
50
49
|
},
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
'file:///path/to/src/HomePage.jsx',
|
|
51
|
+
'Header',
|
|
53
52
|
);
|
|
54
53
|
|
|
55
54
|
export default registerClientReference(
|
|
56
|
-
function() {
|
|
57
|
-
throw new Error(
|
|
55
|
+
function () {
|
|
56
|
+
throw new Error(
|
|
57
|
+
"Attempted to call the default export of file:///path/to/src/HomePage.jsx from the serverbut it's on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of aClient Component.",
|
|
58
|
+
);
|
|
58
59
|
},
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
'file:///path/to/src/HomePage.jsx',
|
|
61
|
+
'default',
|
|
61
62
|
);
|
|
62
63
|
```
|
|
63
64
|
|
|
@@ -114,10 +115,12 @@ If you want to change the file name of the `react-client-manifest.json` file, yo
|
|
|
114
115
|
```js
|
|
115
116
|
const { RSCWebpackPlugin } = require('react-on-rails-rsc/WebpackPlugin');
|
|
116
117
|
|
|
117
|
-
config.plugins.push(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
config.plugins.push(
|
|
119
|
+
new RSCWebpackPlugin({
|
|
120
|
+
isServer: false,
|
|
121
|
+
clientManifestFilename: 'client-components-webpack-manifest.json',
|
|
122
|
+
}),
|
|
123
|
+
);
|
|
121
124
|
```
|
|
122
125
|
|
|
123
126
|
And because React on Rails Pro uploads the `react-client-manifest.json` file to the renderer while uploading the server bundle and it expects it to be named `react-client-manifest.json`, you need to tell React on Rails Pro that the name is changed to `client-components-webpack-manifest.json`.
|
|
@@ -192,6 +195,7 @@ The interesting part is how the RSC payload references the client components. Le
|
|
|
192
195
|
```
|
|
193
196
|
|
|
194
197
|
The RSC payload references client components by including:
|
|
198
|
+
|
|
195
199
|
1. The webpack module ID of the client component (e.g. "./app/javascript/components/ToggleContainer.jsx")
|
|
196
200
|
2. The webpack chunk IDs that contain the component code (e.g. ["client25","js/client25.js"])
|
|
197
201
|
3. The export name being referenced (e.g. "default")
|
|
@@ -222,11 +226,14 @@ In this case, ensure you pass the correct path to `registerServerComponent` func
|
|
|
222
226
|
|
|
223
227
|
```js
|
|
224
228
|
// client/app/packs/client-bundle.js
|
|
225
|
-
import registerServerComponent from 'react-on-rails/registerServerComponent/client';
|
|
229
|
+
import registerServerComponent from 'react-on-rails-pro/registerServerComponent/client';
|
|
226
230
|
|
|
227
|
-
registerServerComponent(
|
|
228
|
-
|
|
229
|
-
|
|
231
|
+
registerServerComponent(
|
|
232
|
+
{
|
|
233
|
+
rscPayloadGenerationUrlPath: 'flight-payload',
|
|
234
|
+
},
|
|
235
|
+
'ReactServerComponentPage',
|
|
236
|
+
);
|
|
230
237
|
```
|
|
231
238
|
|
|
232
239
|
Or if you enabled the `auto_load_bundle` option to make React on Rails automatically register react components, you can pass the path to the `rsc_payload_generation_url_path` config in React on Rails Pro configuration.
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
# Using React Server Components Inside Client Components
|
|
3
2
|
|
|
4
3
|
React on Rails now supports rendering React Server Components (RSC) directly inside React Client Components. This guide explains how to use this feature effectively in your applications.
|
|
@@ -33,7 +32,7 @@ Now, you can render server components directly inside client components using th
|
|
|
33
32
|
|
|
34
33
|
```tsx
|
|
35
34
|
'use client';
|
|
36
|
-
import RSCRoute from 'react-on-rails/RSCRoute';
|
|
35
|
+
import RSCRoute from 'react-on-rails-pro/RSCRoute';
|
|
37
36
|
|
|
38
37
|
export default function ClientComponent() {
|
|
39
38
|
return (
|
|
@@ -52,13 +51,13 @@ Register your server components in your Server and RSC bundles:
|
|
|
52
51
|
|
|
53
52
|
```tsx
|
|
54
53
|
// packs/server_bundle.tsx
|
|
55
|
-
import registerServerComponent from 'react-on-rails/registerServerComponent/server
|
|
54
|
+
import registerServerComponent from 'react-on-rails-pro/registerServerComponent/server';
|
|
56
55
|
import MyServerComponent from './components/MyServerComponent';
|
|
57
56
|
import AnotherServerComponent from './components/AnotherServerComponent';
|
|
58
57
|
|
|
59
58
|
registerServerComponent({
|
|
60
59
|
MyServerComponent,
|
|
61
|
-
AnotherServerComponent
|
|
60
|
+
AnotherServerComponent,
|
|
62
61
|
});
|
|
63
62
|
```
|
|
64
63
|
|
|
@@ -73,7 +72,7 @@ Create a client component that uses `RSCRoute` to render server components:
|
|
|
73
72
|
// components/MyClientComponent.tsx
|
|
74
73
|
'use client';
|
|
75
74
|
import { useState } from 'react';
|
|
76
|
-
import RSCRoute from 'react-on-rails/RSCRoute';
|
|
75
|
+
import RSCRoute from 'react-on-rails-pro/RSCRoute';
|
|
77
76
|
|
|
78
77
|
export default function MyClientComponent({ user }) {
|
|
79
78
|
return (
|
|
@@ -90,31 +89,33 @@ export default function MyClientComponent({ user }) {
|
|
|
90
89
|
Create client and server versions of your component wrapped with `wrapServerComponentRenderer`:
|
|
91
90
|
|
|
92
91
|
#### Client version:
|
|
92
|
+
|
|
93
93
|
```tsx
|
|
94
94
|
// components/MyClientComponent.client.tsx
|
|
95
95
|
'use client';
|
|
96
|
-
import ReactOnRails from 'react-on-rails';
|
|
97
|
-
import wrapServerComponentRenderer from 'react-on-rails/wrapServerComponentRenderer/client';
|
|
96
|
+
import ReactOnRails from 'react-on-rails-pro';
|
|
97
|
+
import wrapServerComponentRenderer from 'react-on-rails-pro/wrapServerComponentRenderer/client';
|
|
98
98
|
import MyClientComponent from './MyClientComponent';
|
|
99
99
|
|
|
100
100
|
const WrappedComponent = wrapServerComponentRenderer(MyClientComponent);
|
|
101
101
|
|
|
102
102
|
ReactOnRails.register({
|
|
103
|
-
MyClientComponent: WrappedComponent
|
|
103
|
+
MyClientComponent: WrappedComponent,
|
|
104
104
|
});
|
|
105
105
|
```
|
|
106
106
|
|
|
107
107
|
#### Server version:
|
|
108
|
+
|
|
108
109
|
```tsx
|
|
109
110
|
// components/MyClientComponent.server.tsx
|
|
110
|
-
import ReactOnRails from 'react-on-rails';
|
|
111
|
-
import wrapServerComponentRenderer from 'react-on-rails/wrapServerComponentRenderer/server';
|
|
111
|
+
import ReactOnRails from 'react-on-rails-pro';
|
|
112
|
+
import wrapServerComponentRenderer from 'react-on-rails-pro/wrapServerComponentRenderer/server';
|
|
112
113
|
import MyClientComponent from './MyClientComponent';
|
|
113
114
|
|
|
114
115
|
const WrappedComponent = wrapServerComponentRenderer(MyClientComponent);
|
|
115
116
|
|
|
116
117
|
ReactOnRails.register({
|
|
117
|
-
MyClientComponent: WrappedComponent
|
|
118
|
+
MyClientComponent: WrappedComponent,
|
|
118
119
|
});
|
|
119
120
|
```
|
|
120
121
|
|
|
@@ -134,7 +135,7 @@ ReactOnRails.register({
|
|
|
134
135
|
```tsx
|
|
135
136
|
'use client';
|
|
136
137
|
import { useState } from 'react';
|
|
137
|
-
import RSCRoute from 'react-on-rails/RSCRoute';
|
|
138
|
+
import RSCRoute from 'react-on-rails-pro/RSCRoute';
|
|
138
139
|
|
|
139
140
|
export default function ClientComponent() {
|
|
140
141
|
const [count, setCount] = useState(0);
|
|
@@ -158,7 +159,7 @@ export default function ClientComponent() {
|
|
|
158
159
|
```tsx
|
|
159
160
|
'use client';
|
|
160
161
|
import { Routes, Route, Link } from 'react-router-dom';
|
|
161
|
-
import RSCRoute from 'react-on-rails/RSCRoute';
|
|
162
|
+
import RSCRoute from 'react-on-rails-pro/RSCRoute';
|
|
162
163
|
import AnotherClientComponent from './AnotherClientComponent';
|
|
163
164
|
|
|
164
165
|
export default function AppRouter({ user }) {
|
|
@@ -190,7 +191,7 @@ The framework supports nesting client and server components to arbitrary depth:
|
|
|
190
191
|
```tsx
|
|
191
192
|
'use client';
|
|
192
193
|
import { Routes, Route } from 'react-router-dom';
|
|
193
|
-
import RSCRoute from 'react-on-rails/RSCRoute';
|
|
194
|
+
import RSCRoute from 'react-on-rails-pro/RSCRoute';
|
|
194
195
|
import ServerRouteLayout from './ServerRouteLayout';
|
|
195
196
|
import ClientRouteLayout from './ClientRouteLayout';
|
|
196
197
|
|
|
@@ -276,7 +277,7 @@ When using server components inside client components:
|
|
|
276
277
|
```tsx
|
|
277
278
|
'use client';
|
|
278
279
|
import { Suspense } from 'react';
|
|
279
|
-
import RSCRoute from 'react-on-rails/RSCRoute';
|
|
280
|
+
import RSCRoute from 'react-on-rails-pro/RSCRoute';
|
|
280
281
|
|
|
281
282
|
export default function ClientComponent({ user }) {
|
|
282
283
|
return (
|
|
@@ -295,17 +296,17 @@ export default function ClientComponent({ user }) {
|
|
|
295
296
|
'use client';
|
|
296
297
|
import { useState } from 'react';
|
|
297
298
|
import { Suspense } from 'react';
|
|
298
|
-
import RSCRoute from 'react-on-rails/RSCRoute';
|
|
299
|
+
import RSCRoute from 'react-on-rails-pro/RSCRoute';
|
|
299
300
|
|
|
300
301
|
export default function ClientComponent({ user }) {
|
|
301
302
|
const [showServerComponent, setShowServerComponent] = useState(false);
|
|
302
|
-
|
|
303
|
+
|
|
303
304
|
return (
|
|
304
305
|
<div>
|
|
305
306
|
<button onClick={() => setShowServerComponent(!showServerComponent)}>
|
|
306
307
|
{showServerComponent ? 'Hide' : 'Show'} Server Component
|
|
307
308
|
</button>
|
|
308
|
-
|
|
309
|
+
|
|
309
310
|
{showServerComponent && (
|
|
310
311
|
<Suspense fallback={<div>Loading...</div>}>
|
|
311
312
|
<RSCRoute componentName="ServerComponent" componentProps={{ user }} />
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
## Why RSC with Streaming?
|
|
4
4
|
|
|
5
5
|
### Waterfall Loading Pattern Benefits
|
|
6
|
+
|
|
6
7
|
React Server Components with streaming is beneficial for most applications, but it's especially powerful for applications with waterfall loading patterns where data dependencies chain together. For example, when you need to load a user profile before loading their posts, or fetch categories before products. Here's why:
|
|
7
8
|
|
|
8
9
|
### How RSC Fixes Waterfall Server Rendering Issues:
|
|
@@ -26,25 +27,26 @@ When a user visits the page, they'll experience the following sequence:
|
|
|
26
27
|
React Server Components significantly reduce client-side JavaScript by:
|
|
27
28
|
|
|
28
29
|
1. **Server-Only Code Elimination:**
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
- Dependencies used only in server components never ship to the client
|
|
31
|
+
- Database queries, API calls, and their libraries stay server-side
|
|
32
|
+
- Heavy data processing utilities remain on the server
|
|
33
|
+
- Server-only NPM packages don't impact client bundle
|
|
33
34
|
|
|
34
35
|
2. **Concrete Examples:**
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
- Routing logic can stay server-side
|
|
37
|
+
- Data fetching libraries (like React Query) are often unnecessary
|
|
38
|
+
- Large formatting libraries (e.g., date-fns, numeral) can be server-only
|
|
39
|
+
- Image processing utilities stay on server
|
|
40
|
+
- Markdown parsers run server-side only
|
|
41
|
+
- Heavy validation libraries remain server-side
|
|
41
42
|
|
|
42
43
|
For example, a typical dashboard might see:
|
|
44
|
+
|
|
43
45
|
```jsx
|
|
44
46
|
// Before: All code shipped to client
|
|
45
47
|
import { format } from 'date-fns'; // ~30KB
|
|
46
|
-
import { marked } from 'marked';
|
|
47
|
-
import numeral from 'numeral';
|
|
48
|
+
import { marked } from 'marked'; // ~35KB
|
|
49
|
+
import numeral from 'numeral'; // ~25KB
|
|
48
50
|
|
|
49
51
|
// After: With RSC, these imports stay server-side
|
|
50
52
|
// Client bundle reduced by ~90KB
|
|
@@ -89,12 +91,14 @@ For example, in a typical page layout:
|
|
|
89
91
|
```
|
|
90
92
|
|
|
91
93
|
With selective hydration:
|
|
94
|
+
|
|
92
95
|
- Navigation could become interactive while Comments are still loading
|
|
93
96
|
- If user tries to click a Sidebar button, it gets priority hydration
|
|
94
97
|
- Each component hydrates independently when ready
|
|
95
98
|
- No waiting for all components to load before any become interactive
|
|
96
99
|
|
|
97
100
|
This approach significantly improves the user experience by:
|
|
101
|
+
|
|
98
102
|
- Reducing Time to Interactive (TTI) for important components
|
|
99
103
|
- Providing faster response to user interactions
|
|
100
104
|
- Maintaining smooth performance even on slower devices or networks
|
|
@@ -102,10 +106,10 @@ This approach significantly improves the user experience by:
|
|
|
102
106
|
|
|
103
107
|
For a deeper dive into selective hydration, see our [Selective Hydration in Streamed Components](./selective-hydration-in-streamed-components.md) guide.
|
|
104
108
|
|
|
105
|
-
|
|
106
109
|
### Comparison with Other Approaches:
|
|
107
110
|
|
|
108
111
|
1. **Full Server Rendering:**
|
|
112
|
+
|
|
109
113
|
- ❌ Delays First Byte until entire page is rendered
|
|
110
114
|
- ❌ All-or-nothing approach to hydration
|
|
111
115
|
- ❌ Must wait for all JavaScript before any interactivity
|
|
@@ -113,6 +117,7 @@ For a deeper dive into selective hydration, see our [Selective Hydration in Stre
|
|
|
113
117
|
- ✅ Complete initial HTML
|
|
114
118
|
|
|
115
119
|
2. **Client-side Lazy Loading:**
|
|
120
|
+
|
|
116
121
|
- ❌ Empty initial HTML for lazy components
|
|
117
122
|
- ❌ Must wait for hydration to load
|
|
118
123
|
- ❌ Poor SEO for lazy content
|
|
@@ -121,6 +126,7 @@ For a deeper dive into selective hydration, see our [Selective Hydration in Stre
|
|
|
121
126
|
- ✅ Reduces initial bundle size
|
|
122
127
|
|
|
123
128
|
3. **RSC with Streaming:**
|
|
129
|
+
|
|
124
130
|
- ✅ Immediate First Byte
|
|
125
131
|
- ✅ Progressive HTML streaming
|
|
126
132
|
- ✅ SEO-friendly for all content
|
|
@@ -132,6 +138,7 @@ For a deeper dive into selective hydration, see our [Selective Hydration in Stre
|
|
|
132
138
|
### 1. Enable RSC Support
|
|
133
139
|
|
|
134
140
|
Add to your Rails initializer, it makes the magic happen 🪄:
|
|
141
|
+
|
|
135
142
|
```ruby
|
|
136
143
|
# config/initializers/react_on_rails_pro.rb
|
|
137
144
|
ReactOnRailsPro.configure do |config|
|
|
@@ -142,13 +149,14 @@ end
|
|
|
142
149
|
### 2. Update Webpack Configuration
|
|
143
150
|
|
|
144
151
|
Create RSC bundle and make it use the RSC loader:
|
|
152
|
+
|
|
145
153
|
```javascript
|
|
146
154
|
// config/webpack/rscWebpackConfig.mjs
|
|
147
155
|
const rscConfig = serverWebpackConfig();
|
|
148
156
|
|
|
149
157
|
// Configure RSC entry point
|
|
150
158
|
rscConfig.entry = {
|
|
151
|
-
'rsc-bundle': rscConfig.entry['server-bundle']
|
|
159
|
+
'rsc-bundle': rscConfig.entry['server-bundle'],
|
|
152
160
|
};
|
|
153
161
|
|
|
154
162
|
// Add RSC loader
|
|
@@ -181,6 +189,7 @@ export default function App() {
|
|
|
181
189
|
```
|
|
182
190
|
|
|
183
191
|
#### 2. Identify Server Component Candidates:
|
|
192
|
+
|
|
184
193
|
- Data fetching components
|
|
185
194
|
- Non-interactive UI
|
|
186
195
|
- Static content sections
|
|
@@ -236,6 +245,7 @@ async function LazyLoadedSection() {
|
|
|
236
245
|
```
|
|
237
246
|
|
|
238
247
|
This migration approach allows you to:
|
|
248
|
+
|
|
239
249
|
- Maintain existing functionality while migrating
|
|
240
250
|
- Incrementally improve performance
|
|
241
251
|
- Test changes in isolation
|
|
@@ -7,18 +7,21 @@ This document explains the rendering flow of React Server Components (RSC) in Re
|
|
|
7
7
|
In a React Server Components project, there are three distinct types of bundles:
|
|
8
8
|
|
|
9
9
|
### RSC Bundle (rsc-bundle.js)
|
|
10
|
+
|
|
10
11
|
- Contains only server components and references to client components
|
|
11
12
|
- Generated using the RSC Webpack Loader which transforms client components into references
|
|
12
13
|
- Used specifically for generating RSC payloads
|
|
13
14
|
- Configured with `react-server` condition to enable RSC-specific code paths that tell the runtime that this bundle is used for RSC payload generation.
|
|
14
15
|
|
|
15
16
|
### Server Bundle (server-bundle.js)
|
|
17
|
+
|
|
16
18
|
- Contains both server and client components in their full form
|
|
17
19
|
- Used for traditional server-side rendering (SSR)
|
|
18
20
|
- Enables HTML generation of any components
|
|
19
21
|
- Does not transform client components into references
|
|
20
22
|
|
|
21
23
|
### Client Bundle
|
|
24
|
+
|
|
22
25
|
- Split into multiple chunks based on client components
|
|
23
26
|
- Each file with `'use client'` directive becomes an entry point
|
|
24
27
|
- Code splitting occurs automatically for client components
|
|
@@ -50,6 +53,7 @@ When a request is made to a page using React Server Components, the following op
|
|
|
50
53
|
- Client components are hydrated progressively without requiring a separate HTTP request
|
|
51
54
|
|
|
52
55
|
This approach offers significant advantages:
|
|
56
|
+
|
|
53
57
|
- Eliminates double rendering of server components
|
|
54
58
|
- Reduces HTTP requests by embedding the RSC payload within the initial HTML response
|
|
55
59
|
- Provides faster interactivity through streamlined rendering and hydration
|
|
@@ -61,7 +65,7 @@ sequenceDiagram
|
|
|
61
65
|
participant NodeRenderer
|
|
62
66
|
participant RSCBundle
|
|
63
67
|
participant ServerBundle
|
|
64
|
-
|
|
68
|
+
|
|
65
69
|
Note over Browser,ServerBundle: 1. Initial Request
|
|
66
70
|
Browser->>RailsView: Request page
|
|
67
71
|
RailsView->>NodeRenderer: stream_react_component
|
|
@@ -69,10 +73,10 @@ sequenceDiagram
|
|
|
69
73
|
ServerBundle->>RSCBundle: generateRSCPayload(component, props)
|
|
70
74
|
RSCBundle-->>ServerBundle: RSC payload with:<br/>- Server components<br/>- Client component refs
|
|
71
75
|
ServerBundle-->>NodeRenderer: Generate HTML using RSC payload
|
|
72
|
-
|
|
76
|
+
|
|
73
77
|
Note over Browser,ServerBundle: 2. Single Response
|
|
74
78
|
NodeRenderer-->>Browser: Stream HTML with embedded RSC payload
|
|
75
|
-
|
|
79
|
+
|
|
76
80
|
Note over Browser: 3. Client Hydration
|
|
77
81
|
Browser->>Browser: Process embedded RSC payload
|
|
78
82
|
loop For each client component
|