react_on_rails_pro 16.2.0.beta.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.controlplane/Dockerfile +49 -0
- data/.controlplane/controlplane.yml +22 -0
- data/.controlplane/gvc.yml +25 -0
- data/.controlplane/postgres.yml +33 -0
- data/.controlplane/rails.yml +49 -0
- data/.controlplane/redis.yml +18 -0
- data/.gitignore +77 -0
- data/.prettierignore +12 -0
- data/.prettierrc +19 -0
- data/.rspec +2 -0
- data/.rubocop.yml +120 -0
- data/.scss-lint.yml +205 -0
- data/CHANGELOG.md +570 -0
- data/CI_SETUP.md +502 -0
- data/CONTRIBUTING.md +376 -0
- data/Dockerfile +63 -0
- data/Gemfile +8 -0
- data/Gemfile.development_dependencies +74 -0
- data/Gemfile.loader +32 -0
- data/Gemfile.lock +527 -0
- data/LICENSE +98 -0
- data/LICENSE_SETUP.md +272 -0
- data/README.md +577 -0
- data/Rakefile +13 -0
- data/app/controllers/react_on_rails_pro/rsc_payload_controller.rb +7 -0
- data/app/helpers/react_on_rails_pro_helper.rb +360 -0
- data/app/views/react_on_rails_pro/rsc_payload.html.erb +1 -0
- data/babel.config.js +4 -0
- data/docs/bundle-caching.md +205 -0
- data/docs/caching.md +234 -0
- data/docs/code-splitting-loadable-components.md +313 -0
- data/docs/code-splitting.md +349 -0
- data/docs/configuration.md +165 -0
- data/docs/contributors-info/onboarding-customers.md +6 -0
- data/docs/contributors-info/releasing.md +40 -0
- data/docs/contributors-info/style.md +33 -0
- data/docs/home-pro.md +146 -0
- data/docs/installation.md +203 -0
- data/docs/js-memory-leaks.md +22 -0
- data/docs/node-renderer/basics.md +92 -0
- data/docs/node-renderer/debugging.md +38 -0
- data/docs/node-renderer/error-reporting-and-tracing.md +160 -0
- data/docs/node-renderer/heroku.md +102 -0
- data/docs/node-renderer/js-configuration.md +91 -0
- data/docs/node-renderer/troubleshooting.md +5 -0
- data/docs/profiling-server-side-rendering-code.md +179 -0
- data/docs/react-server-components/add-streaming-and-interactivity.md +190 -0
- data/docs/react-server-components/create-without-ssr.md +448 -0
- data/docs/react-server-components/glossary.md +102 -0
- data/docs/react-server-components/how-react-server-components-work.md +243 -0
- data/docs/react-server-components/inside-client-components.md +332 -0
- data/docs/react-server-components/purpose-and-benefits.md +243 -0
- data/docs/react-server-components/rendering-flow.md +86 -0
- data/docs/react-server-components/selective-hydration-in-streamed-components.md +75 -0
- data/docs/react-server-components/server-side-rendering.md +72 -0
- data/docs/react-server-components/tutorial.md +19 -0
- data/docs/release-notes/4.0.md +94 -0
- data/docs/release-notes/v4-react-server-components.md +66 -0
- data/docs/ruby-api.md +11 -0
- data/docs/streaming-server-rendering.md +210 -0
- data/docs/troubleshooting.md +24 -0
- data/docs/updating.md +219 -0
- data/eslint.config.mjs +220 -0
- data/lib/react_on_rails_pro/assets_precompile.rb +230 -0
- data/lib/react_on_rails_pro/cache.rb +88 -0
- data/lib/react_on_rails_pro/concerns/rsc_payload_renderer.rb +38 -0
- data/lib/react_on_rails_pro/concerns/stream.rb +103 -0
- data/lib/react_on_rails_pro/configuration.rb +228 -0
- data/lib/react_on_rails_pro/constants.rb +8 -0
- data/lib/react_on_rails_pro/engine.rb +24 -0
- data/lib/react_on_rails_pro/error.rb +14 -0
- data/lib/react_on_rails_pro/license_public_key.rb +30 -0
- data/lib/react_on_rails_pro/license_validator.rb +188 -0
- data/lib/react_on_rails_pro/prepare_node_renderer_bundles.rb +40 -0
- data/lib/react_on_rails_pro/rendering_error.rb +5 -0
- data/lib/react_on_rails_pro/request.rb +318 -0
- data/lib/react_on_rails_pro/routes.rb +13 -0
- data/lib/react_on_rails_pro/server_rendering_js_code.rb +102 -0
- data/lib/react_on_rails_pro/server_rendering_pool/node_rendering_pool.rb +133 -0
- data/lib/react_on_rails_pro/server_rendering_pool/pro_rendering.rb +117 -0
- data/lib/react_on_rails_pro/stream_cache.rb +61 -0
- data/lib/react_on_rails_pro/stream_request.rb +170 -0
- data/lib/react_on_rails_pro/utils.rb +222 -0
- data/lib/react_on_rails_pro/v8_log_processor.rb +50 -0
- data/lib/react_on_rails_pro/version.rb +6 -0
- data/lib/react_on_rails_pro.rb +23 -0
- data/package-scripts.yml +109 -0
- data/package.json +159 -0
- data/rakelib/dummy_apps.rake +22 -0
- data/rakelib/lint.rake +32 -0
- data/rakelib/public_key_management.rake +155 -0
- data/rakelib/rbs.rake +47 -0
- data/rakelib/run_rspec.rake +81 -0
- data/rakelib/task_helpers.rb +45 -0
- data/rakelib/yard.rake +20 -0
- data/react_on_rails_pro.gemspec +47 -0
- data/readme-gen-docs.md +1 -0
- data/script/bootstrap +33 -0
- data/script/preinstall.js +31 -0
- data/script/setup +23 -0
- data/script/test +38 -0
- data/sig/react_on_rails_pro/cache.rbs +13 -0
- data/sig/react_on_rails_pro/configuration.rbs +100 -0
- data/sig/react_on_rails_pro/error.rbs +4 -0
- data/sig/react_on_rails_pro/utils.rbs +7 -0
- data/sig/react_on_rails_pro.rbs +5 -0
- data/yarn.lock +7599 -0
- metadata +319 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# How React Server Components work
|
|
2
|
+
|
|
3
|
+
React Server Components (RSC) enable server-side component execution with client-side streaming. This document explains the underlying mechanisms and technical details of how RSC works under the hood.
|
|
4
|
+
|
|
5
|
+
## Bundling Process
|
|
6
|
+
|
|
7
|
+
We showed in the [Create a React Server Component without SSR](./create-without-ssr.md) article how to bundle React Server Components. During bundling, we used:
|
|
8
|
+
|
|
9
|
+
### RSC Webpack Loader
|
|
10
|
+
|
|
11
|
+
The `react-on-rails-rsc/WebpackLoader` is a custom loader that removes the client components and their dependencies from the RSC bundle and replace them with client references that tell the rsc runtime that there is a client component entry point in this place.
|
|
12
|
+
|
|
13
|
+
The RSC Webpack Loader works when it finds a file with the `'use client'` directive on top of it.
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
// app/javascript/client/app/components/HomePage.jsx
|
|
17
|
+
'use client';
|
|
18
|
+
|
|
19
|
+
import Footer from './Footer';
|
|
20
|
+
|
|
21
|
+
export const Header = () => {
|
|
22
|
+
return <div>Header</div>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default function HomePage() {
|
|
26
|
+
return (
|
|
27
|
+
<div>
|
|
28
|
+
<Header />
|
|
29
|
+
<div>Home Page</div>
|
|
30
|
+
<Footer />
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
It replaces all exports of the file with the client references.
|
|
37
|
+
|
|
38
|
+
> [!NOTE]
|
|
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
|
+
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
import { registerClientReference } from "react-server-dom-webpack/server";
|
|
44
|
+
|
|
45
|
+
export const Header = registerClientReference(
|
|
46
|
+
function () {
|
|
47
|
+
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."
|
|
49
|
+
);
|
|
50
|
+
},
|
|
51
|
+
"file:///path/to/src/HomePage.jsx",
|
|
52
|
+
"Header"
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
export default registerClientReference(
|
|
56
|
+
function() {
|
|
57
|
+
throw new Error("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
|
+
},
|
|
59
|
+
"file:///path/to/src/HomePage.jsx",
|
|
60
|
+
"default"
|
|
61
|
+
);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
When a file is marked with `'use client'`, the RSC Webpack Loader replaces all component exports with `ClientReference` objects. The `registerClientReference` function takes three arguments:
|
|
65
|
+
|
|
66
|
+
1. A function that throws an error if someone tries to call the component function directly instead of rendering it as a component
|
|
67
|
+
2. A string representing the file path of the client component, which serves as part of its unique identifier
|
|
68
|
+
3. A string with the export name ("default" for default exports, or the named export identifier)
|
|
69
|
+
|
|
70
|
+
The second and third arguments are used to identify the client component when it needs to be hydrated in the browser.
|
|
71
|
+
|
|
72
|
+
Note that all imports from the original file are removed in the transformed code. This includes both the `Footer` component import and any other dependencies that the client components may have used. The client component implementations and their dependencies are removed from the RSC bundle.
|
|
73
|
+
|
|
74
|
+
### RSC Client Plugin
|
|
75
|
+
|
|
76
|
+
We also used `react-on-rails-rsc/WebpackPlugin` with the client bundle. It does the following:
|
|
77
|
+
|
|
78
|
+
1. Adds all files with the `'use client'` directive on top of it as entry points to the client bundle.
|
|
79
|
+
2. Creates the `react-client-manifest.json` file that contains the mapping of the client components files to their corresponding webpack chunk IDs.
|
|
80
|
+
|
|
81
|
+
Let's examine the `react-client-manifest.json` file.
|
|
82
|
+
|
|
83
|
+
> [!NOTE]
|
|
84
|
+
> 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.
|
|
85
|
+
|
|
86
|
+
First, you need to build the client bundle by running:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
CLIENT_BUNDLE_ONLY=true bin/shakapacker
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
> [!NOTE]
|
|
93
|
+
> When you run `bin/dev`, the client bundle may not be written to the disk, it's served from the webpack-dev-server. That's why you need to run `CLIENT_BUNDLE_ONLY=true bin/shakapacker` to ensure the client bundle is built and written to the disk.
|
|
94
|
+
|
|
95
|
+
Then, you can find the `react-client-manifest.json` file in the `public/webpack/development` or `public/webpack/production` directory, depending on the environment you are building for.
|
|
96
|
+
|
|
97
|
+
Let's search for the client component `ToggleContainer` that we built before in [Add Streaming and Interactivity to RSC Page](./add-streaming-and-interactivity.md) article. You will find the following entry in the `react-client-manifest.json` file:
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
"file:///path/to/app/javascript/components/ToggleContainer.jsx": {
|
|
101
|
+
"id": "./app/javascript/components/ToggleContainer.jsx",
|
|
102
|
+
"chunks": [
|
|
103
|
+
"client25",
|
|
104
|
+
"js/client25.js"
|
|
105
|
+
],
|
|
106
|
+
"name": "*"
|
|
107
|
+
},
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
This entry indicates that the `ToggleContainer` client component is included in the `client25` chunk. The `js/client25.js` file contains the client-side code for the `ToggleContainer` component. You can find the `client25` chunk in the `public/webpack/<environment>/js/client25.js` file. Also, the `id` field is the Webpack module ID for the `ToggleContainer` client component. It's used by react runtime to load and hydrate the component in the browser.
|
|
111
|
+
|
|
112
|
+
If you want to change the file name of the `react-client-manifest.json` file, you can do so by setting the `clientManifestFilename` option in the `react-on-rails-rsc/WebpackPlugin` plugin as follows:
|
|
113
|
+
|
|
114
|
+
```js
|
|
115
|
+
const { RSCWebpackPlugin } = require('react-on-rails-rsc/WebpackPlugin');
|
|
116
|
+
|
|
117
|
+
config.plugins.push(new RSCWebpackPlugin({
|
|
118
|
+
isServer: false,
|
|
119
|
+
clientManifestFilename: 'client-components-webpack-manifest.json',
|
|
120
|
+
}));
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
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`.
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
# config/initializers/react_on_rails.rb
|
|
127
|
+
ReactOnRails.configure do |config|
|
|
128
|
+
config.react_client_manifest_file = "client-components-webpack-manifest.json"
|
|
129
|
+
end
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## React Server Component Payload (RSC Payload)
|
|
133
|
+
|
|
134
|
+
The React Server Component Payload (RSC Payload) is a mechanism that allows you to pass the rendered server components from the server to the client. You can use the `rsc_payload_react_component` helper function to embed the RSC payload of any component in your Rails views. Let's try to embed the RSC payload of the `ReactServerComponentPage` component in the `app/views/pages/react_server_component_page_rsc_payload.html.erb` view.
|
|
135
|
+
|
|
136
|
+
```erb
|
|
137
|
+
<%= rsc_payload_react_component("ReactServerComponentPage") %>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Add the route to the `app/config/routes.rb` file.
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
# config/routes.rb
|
|
144
|
+
get "/react_server_component_page_rsc_payload", to: "pages#react_server_component_page_rsc_payload"
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
And render the view using the `stream_view_containing_react_components` helper method.
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
# app/controllers/pages_controller.rb
|
|
151
|
+
class PagesController < ApplicationController
|
|
152
|
+
include ReactOnRailsPro::Stream
|
|
153
|
+
|
|
154
|
+
def react_server_component_page_rsc_payload
|
|
155
|
+
stream_view_containing_react_components(template: "pages/react_server_component_page_rsc_payload")
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
When you navigate to the `http://localhost:3000/react_server_component_page_rsc_payload` page, you will see the RSC payload of the `ReactServerComponentPage` component. You will find multiple JSON objects in the response body. Each represents a chunk of the RSC payload.
|
|
161
|
+
|
|
162
|
+
```json
|
|
163
|
+
{
|
|
164
|
+
"html":"<RSC Payload>",
|
|
165
|
+
"consoleReplayScript":"",
|
|
166
|
+
"hasErrors":false,
|
|
167
|
+
"isShellReady":true
|
|
168
|
+
}
|
|
169
|
+
{
|
|
170
|
+
"html":"<RSC Payload>",
|
|
171
|
+
"consoleReplayScript":"",
|
|
172
|
+
"hasErrors":false,
|
|
173
|
+
"isShellReady":true
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
The real RSC payload is embedded in the `html` field. Other fields are used by React on Rails Pro to ensure the RSC payload is rendered correctly and to replay the console logs in the browser.
|
|
178
|
+
|
|
179
|
+
> [!NOTE]
|
|
180
|
+
> using `html` field to refer to the RSC payload may be confusing. It will be changed later to `rscPayload`, but it's an implementation detail and you should not rely on it.
|
|
181
|
+
|
|
182
|
+
The RSC payload itself is an implementation detail, you don't need to understand it to use React Server Components. But we can notice that it contains the React render tree of the `ReactServerComponentPage` component. Like this:
|
|
183
|
+
|
|
184
|
+
```rsc
|
|
185
|
+
1:["$","div",null,{"className":"server-component-demo","children":[["$","h2",null,{"children":"React Server Component Demo"}],["$","section",null,{"children":[["$","h3",null,{"children":"Date Calculations (using moment.js)"}]]}]]}]
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The interesting part is how the RSC payload references the client components. Let's take a look at how it references the `ToggleContainer` client component.
|
|
189
|
+
|
|
190
|
+
```rsc
|
|
191
|
+
7:I["./app/javascript/components/ToggleContainer.jsx",["client25","js/client25.js"],"default"]
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The RSC payload references client components by including:
|
|
195
|
+
1. The webpack module ID of the client component (e.g. "./app/javascript/components/ToggleContainer.jsx")
|
|
196
|
+
2. The webpack chunk IDs that contain the component code (e.g. ["client25","js/client25.js"])
|
|
197
|
+
3. The export name being referenced (e.g. "default")
|
|
198
|
+
|
|
199
|
+
This information comes from the `react-client-manifest.json` file, which maps client component paths to their corresponding webpack module and chunk IDs. That's why we needed to upload the `react-client-manifest.json` file to the renderer as it's needed to generate the RSC payload.
|
|
200
|
+
|
|
201
|
+
## Automatically Generate the RSC Payload
|
|
202
|
+
|
|
203
|
+
Usually, you don't need to generate the RSC payload manually. You can use the `rsc_payload_route` helper method inside the `config/routes.rb` file to automatically add the rsc route that accepts the component name as a parameter and returns the RSC payload.
|
|
204
|
+
|
|
205
|
+
```ruby
|
|
206
|
+
# config/routes.rb
|
|
207
|
+
Rails.application.routes.draw do
|
|
208
|
+
rsc_payload_route
|
|
209
|
+
end
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
You can change the path of the rsc route by passing the `path` option to the `rsc_payload_route` method.
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
# config/routes.rb
|
|
216
|
+
Rails.application.routes.draw do
|
|
217
|
+
rsc_payload_route path: "/flight-payload"
|
|
218
|
+
end
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
In this case, ensure you pass the correct path to `registerServerComponent` function in the client bundle.
|
|
222
|
+
|
|
223
|
+
```js
|
|
224
|
+
// client/app/packs/client-bundle.js
|
|
225
|
+
import registerServerComponent from 'react-on-rails/registerServerComponent/client';
|
|
226
|
+
|
|
227
|
+
registerServerComponent({
|
|
228
|
+
rscPayloadGenerationUrlPath: "flight-payload",
|
|
229
|
+
}, "ReactServerComponentPage")
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
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.
|
|
233
|
+
|
|
234
|
+
```ruby
|
|
235
|
+
# config/initializers/react_on_rails.rb
|
|
236
|
+
ReactOnRailsPro.configure do |config|
|
|
237
|
+
config.rsc_payload_generation_url_path = "flight-payload"
|
|
238
|
+
end
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Next Steps
|
|
242
|
+
|
|
243
|
+
To learn more about how React Server Components are rendered in React on Rails Pro, including the rendering flow, bundle types, and upcoming improvements, see [React Server Components Rendering Flow](./rendering-flow.md).
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
|
|
2
|
+
# Using React Server Components Inside Client Components
|
|
3
|
+
|
|
4
|
+
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.
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
React Server Components provide several benefits.However, React traditionally doesn't allow server components to be directly rendered inside client components. This feature bypasses that limitation.
|
|
9
|
+
|
|
10
|
+
> [!IMPORTANT]
|
|
11
|
+
> This feature should be used judiciously. It's best suited for server components whose props change very rarely, such as router routes. **Do not** use this with components whose props change frequently as it triggers HTTP requests to the server on each re-render.
|
|
12
|
+
|
|
13
|
+
## Basic Usage
|
|
14
|
+
|
|
15
|
+
### Before
|
|
16
|
+
|
|
17
|
+
Previously, server components could only be embedded inside client components if passed as a prop from a parent server component:
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
// Parent Server Component
|
|
21
|
+
export default function Parent() {
|
|
22
|
+
return (
|
|
23
|
+
<ClientComponent>
|
|
24
|
+
<ServerComponent />
|
|
25
|
+
</ClientComponent>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### After
|
|
31
|
+
|
|
32
|
+
Now, you can render server components directly inside client components using the `RSCRoute` component:
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
'use client';
|
|
36
|
+
import RSCRoute from 'react-on-rails/RSCRoute';
|
|
37
|
+
|
|
38
|
+
export default function ClientComponent() {
|
|
39
|
+
return (
|
|
40
|
+
<div>
|
|
41
|
+
<RSCRoute componentName="ServerComponent" componentProps={{ user }} />
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Setup Steps
|
|
48
|
+
|
|
49
|
+
### 1. Register your server components
|
|
50
|
+
|
|
51
|
+
Register your server components in your Server and RSC bundles:
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
// packs/server_bundle.tsx
|
|
55
|
+
import registerServerComponent from 'react-on-rails/registerServerComponent/server.rsc';
|
|
56
|
+
import MyServerComponent from './components/MyServerComponent';
|
|
57
|
+
import AnotherServerComponent from './components/AnotherServerComponent';
|
|
58
|
+
|
|
59
|
+
registerServerComponent({
|
|
60
|
+
MyServerComponent,
|
|
61
|
+
AnotherServerComponent
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
> [!NOTE]
|
|
66
|
+
> Server components only need to be registered in the client bundle if they will be rendered directly in Rails views using the `stream_react_component` helper. If you're only using server components inside client components via `RSCRoute`, you can skip client bundle registration entirely. In this case, it's enough to register the server component in the server and RSC bundles.
|
|
67
|
+
|
|
68
|
+
### 2. Create your client component
|
|
69
|
+
|
|
70
|
+
Create a client component that uses `RSCRoute` to render server components:
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
// components/MyClientComponent.tsx
|
|
74
|
+
'use client';
|
|
75
|
+
import { useState } from 'react';
|
|
76
|
+
import RSCRoute from 'react-on-rails/RSCRoute';
|
|
77
|
+
|
|
78
|
+
export default function MyClientComponent({ user }) {
|
|
79
|
+
return (
|
|
80
|
+
<div>
|
|
81
|
+
<h1>Hello from Client Component</h1>
|
|
82
|
+
<RSCRoute componentName="MyServerComponent" componentProps={{ user }} />
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 3. Wrap your client component
|
|
89
|
+
|
|
90
|
+
Create client and server versions of your component wrapped with `wrapServerComponentRenderer`:
|
|
91
|
+
|
|
92
|
+
#### Client version:
|
|
93
|
+
```tsx
|
|
94
|
+
// components/MyClientComponent.client.tsx
|
|
95
|
+
'use client';
|
|
96
|
+
import ReactOnRails from 'react-on-rails';
|
|
97
|
+
import wrapServerComponentRenderer from 'react-on-rails/wrapServerComponentRenderer/client';
|
|
98
|
+
import MyClientComponent from './MyClientComponent';
|
|
99
|
+
|
|
100
|
+
const WrappedComponent = wrapServerComponentRenderer(MyClientComponent);
|
|
101
|
+
|
|
102
|
+
ReactOnRails.register({
|
|
103
|
+
MyClientComponent: WrappedComponent
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
#### Server version:
|
|
108
|
+
```tsx
|
|
109
|
+
// components/MyClientComponent.server.tsx
|
|
110
|
+
import ReactOnRails from 'react-on-rails';
|
|
111
|
+
import wrapServerComponentRenderer from 'react-on-rails/wrapServerComponentRenderer/server';
|
|
112
|
+
import MyClientComponent from './MyClientComponent';
|
|
113
|
+
|
|
114
|
+
const WrappedComponent = wrapServerComponentRenderer(MyClientComponent);
|
|
115
|
+
|
|
116
|
+
ReactOnRails.register({
|
|
117
|
+
MyClientComponent: WrappedComponent
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 4. Use in Rails view
|
|
122
|
+
|
|
123
|
+
```erb
|
|
124
|
+
<%= stream_react_component('MyClientComponent', props: { user: current_user.as_json }, prerender: true) %>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
> [!NOTE]
|
|
128
|
+
> You must use `stream_react_component` rather than `react_component` for server components or client components that use server components.
|
|
129
|
+
|
|
130
|
+
## Use Cases and Examples
|
|
131
|
+
|
|
132
|
+
### ❌ Bad Example - Frequently Changing Props
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
'use client';
|
|
136
|
+
import { useState } from 'react';
|
|
137
|
+
import RSCRoute from 'react-on-rails/RSCRoute';
|
|
138
|
+
|
|
139
|
+
export default function ClientComponent() {
|
|
140
|
+
const [count, setCount] = useState(0);
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<div>
|
|
144
|
+
<button onClick={() => setCount(count + 1)}>Increment</button>
|
|
145
|
+
<label>Count: {count}</label>
|
|
146
|
+
{/* BAD EXAMPLE: Server Component props change with each button click */}
|
|
147
|
+
<RSCRoute componentName="ServerComponent" componentProps={{ count }} />
|
|
148
|
+
</div>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
> [!WARNING]
|
|
154
|
+
> This implementation will make a server request on every state change, significantly impacting performance.
|
|
155
|
+
|
|
156
|
+
### ✅ Good Example - Router Integration
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
'use client';
|
|
160
|
+
import { Routes, Route, Link } from 'react-router-dom';
|
|
161
|
+
import RSCRoute from 'react-on-rails/RSCRoute';
|
|
162
|
+
import AnotherClientComponent from './AnotherClientComponent';
|
|
163
|
+
|
|
164
|
+
export default function AppRouter({ user }) {
|
|
165
|
+
return (
|
|
166
|
+
<>
|
|
167
|
+
<nav>
|
|
168
|
+
<Link to="/">Home</Link>
|
|
169
|
+
<Link to="/about">About</Link>
|
|
170
|
+
<Link to="/client-component">Client Component</Link>
|
|
171
|
+
</nav>
|
|
172
|
+
<Routes>
|
|
173
|
+
{/* Mix client and server components in your router */}
|
|
174
|
+
<Route path="/client-component" element={<AnotherClientComponent />} />
|
|
175
|
+
{/* GOOD EXAMPLE: Server Component props rarely change */}
|
|
176
|
+
<Route path="/about" element={<RSCRoute componentName="About" componentProps={{ user }} />} />
|
|
177
|
+
<Route path="/" element={<RSCRoute componentName="Home" componentProps={{ user }} />} />
|
|
178
|
+
</Routes>
|
|
179
|
+
</>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Advanced Usage
|
|
185
|
+
|
|
186
|
+
### Nested Routes with Server Components
|
|
187
|
+
|
|
188
|
+
The framework supports nesting client and server components to arbitrary depth:
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
'use client';
|
|
192
|
+
import { Routes, Route } from 'react-router-dom';
|
|
193
|
+
import RSCRoute from 'react-on-rails/RSCRoute';
|
|
194
|
+
import ServerRouteLayout from './ServerRouteLayout';
|
|
195
|
+
import ClientRouteLayout from './ClientRouteLayout';
|
|
196
|
+
|
|
197
|
+
export default function AppRouter() {
|
|
198
|
+
return (
|
|
199
|
+
<Routes>
|
|
200
|
+
<Route path="/main-server-route" element={<ServerRouteLayout />}>
|
|
201
|
+
<Route path="/server-subroute" element={<RSCRoute componentName="MyServerComponent" />} />
|
|
202
|
+
<Route path="/client-subroute" element={<ClientSubcomponent />} />
|
|
203
|
+
</Route>
|
|
204
|
+
<Route path="/main-client-route" element={<ClientRouteLayout />}>
|
|
205
|
+
<Route path="/client-subroute" element={<ClientSubcomponent />} />
|
|
206
|
+
<Route path="/server-subroute" element={<RSCRoute componentName="MyServerComponent" />} />
|
|
207
|
+
</Route>
|
|
208
|
+
</Routes>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Using `Outlet` in Server Components
|
|
214
|
+
|
|
215
|
+
To use React Router's `Outlet` in server components, create a client version:
|
|
216
|
+
|
|
217
|
+
```tsx
|
|
218
|
+
// ./components/Outlet.tsx
|
|
219
|
+
'use client';
|
|
220
|
+
export { Outlet as default } from 'react-router-dom';
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Then use it in your server components:
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
// ./components/ServerRouteLayout.tsx
|
|
227
|
+
import Outlet from './Outlet';
|
|
228
|
+
|
|
229
|
+
export default function ServerRouteLayout() {
|
|
230
|
+
return (
|
|
231
|
+
<div>
|
|
232
|
+
<h1>Server Route Layout</h1>
|
|
233
|
+
<Outlet />
|
|
234
|
+
</div>
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Auto-Loading Bundles
|
|
240
|
+
|
|
241
|
+
If you're using the `auto_load_bundle: true` option in your React on Rails configuration, you don't need to manually register components using `ReactOnRails.register`. However, you still need to:
|
|
242
|
+
|
|
243
|
+
1. Create both client and server wrappers for your components
|
|
244
|
+
2. Properly import the environment-specific implementations of `wrapServerComponentRenderer`
|
|
245
|
+
|
|
246
|
+
## Component Lifecycle
|
|
247
|
+
|
|
248
|
+
When using server components inside client components:
|
|
249
|
+
|
|
250
|
+
1. **During Initial SSR**:
|
|
251
|
+
- The server component is rendered on the server
|
|
252
|
+
- Its payload is embedded directly in the HTML response
|
|
253
|
+
- No additional HTTP requests are needed for hydration
|
|
254
|
+
|
|
255
|
+
2. **During Client Navigation**:
|
|
256
|
+
- When a user navigates to a new route client-side
|
|
257
|
+
- The client makes an HTTP request to fetch the server component payload
|
|
258
|
+
- The route is rendered with the fetched server component
|
|
259
|
+
|
|
260
|
+
3. **During State Changes**:
|
|
261
|
+
- If a server component's props change, a new HTTP request is made
|
|
262
|
+
- The component is re-rendered with the new props
|
|
263
|
+
- This is why you should avoid frequently changing props
|
|
264
|
+
|
|
265
|
+
## Performance Considerations
|
|
266
|
+
|
|
267
|
+
- Page responsiveness is improved because RSC payloads are embedded in the HTML and no additional HTTP requests are needed for hydration
|
|
268
|
+
- Client navigation to new routes with server components requires an HTTP request
|
|
269
|
+
- Avoid changing server component props frequently
|
|
270
|
+
- Consider using suspense boundaries for loading states during navigation
|
|
271
|
+
|
|
272
|
+
## Common Patterns
|
|
273
|
+
|
|
274
|
+
### Using a Loading State
|
|
275
|
+
|
|
276
|
+
```tsx
|
|
277
|
+
'use client';
|
|
278
|
+
import { Suspense } from 'react';
|
|
279
|
+
import RSCRoute from 'react-on-rails/RSCRoute';
|
|
280
|
+
|
|
281
|
+
export default function ClientComponent({ user }) {
|
|
282
|
+
return (
|
|
283
|
+
<div>
|
|
284
|
+
<Suspense fallback={<div>Loading server component...</div>}>
|
|
285
|
+
<RSCRoute componentName="ServerComponent" componentProps={{ user }} />
|
|
286
|
+
</Suspense>
|
|
287
|
+
</div>
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Conditional Rendering
|
|
293
|
+
|
|
294
|
+
```tsx
|
|
295
|
+
'use client';
|
|
296
|
+
import { useState } from 'react';
|
|
297
|
+
import { Suspense } from 'react';
|
|
298
|
+
import RSCRoute from 'react-on-rails/RSCRoute';
|
|
299
|
+
|
|
300
|
+
export default function ClientComponent({ user }) {
|
|
301
|
+
const [showServerComponent, setShowServerComponent] = useState(false);
|
|
302
|
+
|
|
303
|
+
return (
|
|
304
|
+
<div>
|
|
305
|
+
<button onClick={() => setShowServerComponent(!showServerComponent)}>
|
|
306
|
+
{showServerComponent ? 'Hide' : 'Show'} Server Component
|
|
307
|
+
</button>
|
|
308
|
+
|
|
309
|
+
{showServerComponent && (
|
|
310
|
+
<Suspense fallback={<div>Loading...</div>}>
|
|
311
|
+
<RSCRoute componentName="ServerComponent" componentProps={{ user }} />
|
|
312
|
+
</Suspense>
|
|
313
|
+
)}
|
|
314
|
+
</div>
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
> [!NOTE]
|
|
320
|
+
> When conditionally rendering server components, an HTTP request will be made when the component becomes visible.
|
|
321
|
+
|
|
322
|
+
## Best Practices
|
|
323
|
+
|
|
324
|
+
1. **Use for rarely changing components**: Server components are ideal for routes, layouts, and content that doesn't change frequently.
|
|
325
|
+
|
|
326
|
+
2. **Always wrap in Suspense**: Server components may load asynchronously, especially after client navigation.
|
|
327
|
+
|
|
328
|
+
3. **Pass stable props**: Avoid passing state variables that change frequently as props to server components.
|
|
329
|
+
|
|
330
|
+
4. **Use for data-heavy components**: Components that need to fetch data from databases or APIs are good candidates for server components.
|
|
331
|
+
|
|
332
|
+
By following these guidelines, you can effectively leverage React Server Components while maintaining optimal performance.
|