react_on_rails_pro 16.2.0.test.6 → 16.2.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/.prettierignore +3 -0
  3. data/.rubocop.yml +7 -90
  4. data/CHANGELOG.md +16 -7
  5. data/CONTRIBUTING.md +64 -43
  6. data/Gemfile.development_dependencies +4 -4
  7. data/Gemfile.loader +11 -8
  8. data/Gemfile.lock +147 -124
  9. data/README.md +1 -1
  10. data/docs/bundle-caching.md +22 -8
  11. data/docs/caching.md +39 -27
  12. data/docs/code-splitting-loadable-components.md +71 -55
  13. data/docs/code-splitting.md +74 -70
  14. data/docs/configuration.md +6 -6
  15. data/docs/contributors-info/onboarding-customers.md +2 -1
  16. data/docs/contributors-info/releasing.md +1 -0
  17. data/docs/contributors-info/style.md +23 -15
  18. data/docs/home-pro.md +33 -15
  19. data/docs/installation.md +57 -9
  20. data/docs/js-memory-leaks.md +2 -3
  21. data/docs/node-renderer/debugging.md +5 -1
  22. data/docs/node-renderer/error-reporting-and-tracing.md +27 -15
  23. data/docs/node-renderer/heroku.md +4 -5
  24. data/docs/profiling-server-side-rendering-code.md +43 -42
  25. data/docs/react-server-components/add-streaming-and-interactivity.md +1 -1
  26. data/docs/react-server-components/create-without-ssr.md +18 -18
  27. data/docs/react-server-components/glossary.md +22 -3
  28. data/docs/react-server-components/how-react-server-components-work.md +25 -18
  29. data/docs/react-server-components/inside-client-components.md +19 -18
  30. data/docs/react-server-components/purpose-and-benefits.md +24 -14
  31. data/docs/react-server-components/rendering-flow.md +7 -3
  32. data/docs/react-server-components/server-side-rendering.md +23 -22
  33. data/docs/release-notes/4.0.md +103 -94
  34. data/docs/release-notes/v4-react-server-components.md +16 -16
  35. data/docs/streaming-server-rendering.md +2 -4
  36. data/docs/troubleshooting.md +5 -2
  37. data/docs/updating.md +55 -20
  38. data/lib/react_on_rails_pro/request.rb +18 -3
  39. data/lib/react_on_rails_pro/version.rb +1 -1
  40. data/rakelib/dummy_apps.rake +4 -4
  41. data/rakelib/lint.rake +1 -1
  42. data/rakelib/run_rspec.rake +3 -3
  43. metadata +4 -4
@@ -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 "react-server-dom-webpack/server";
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
- "file:///path/to/src/HomePage.jsx",
52
- "Header"
50
+ 'file:///path/to/src/HomePage.jsx',
51
+ 'Header',
53
52
  );
54
53
 
55
54
  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.");
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
- "file:///path/to/src/HomePage.jsx",
60
- "default"
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(new RSCWebpackPlugin({
118
- isServer: false,
119
- clientManifestFilename: 'client-components-webpack-manifest.json',
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
- rscPayloadGenerationUrlPath: "flight-payload",
229
- }, "ReactServerComponentPage")
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.rsc';
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
- - Dependencies used only in server components never ship to the client
30
- - Database queries, API calls, and their libraries stay server-side
31
- - Heavy data processing utilities remain on the server
32
- - Server-only NPM packages don't impact client bundle
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
- - Routing logic can stay server-side
36
- - Data fetching libraries (like React Query) are often unnecessary
37
- - Large formatting libraries (e.g., date-fns, numeral) can be server-only
38
- - Image processing utilities stay on server
39
- - Markdown parsers run server-side only
40
- - Heavy validation libraries remain server-side
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'; // ~35KB
47
- import numeral from 'numeral'; // ~25KB
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
@@ -1,6 +1,7 @@
1
1
  # SSR React Server Components
2
2
 
3
3
  Before reading this document, please read:
4
+
4
5
  1. [Create React Server Component without SSR](./create-without-ssr.md)
5
6
  2. [Add Streaming and Interactivity to RSC Page](./add-streaming-and-interactivity.md)
6
7
 
@@ -13,7 +14,7 @@ Let's make React on Rails server-side render the React Server Component Page we
13
14
  Update the `react_server_component_without_ssr.html.erb` view to pass `prerender: true` to the `react_component` helper.
14
15
 
15
16
  ```erb
16
- <%= react_component("ReactServerComponentPage",
17
+ <%= react_component("ReactServerComponentPage",
17
18
  prerender: true,
18
19
  trace: true,
19
20
  id: "ReactServerComponentPage-react-component-0") %>
@@ -37,33 +38,33 @@ To enable streaming SSR for React Server Components, we need to:
37
38
 
38
39
  1. Create a new view called `react_server_component_ssr.html.erb` with the following content:
39
40
 
40
- ```erb
41
- # app/views/pages/react_server_component_ssr.html.erb
42
- <%= stream_react_component("ReactServerComponentPage",
43
- id: "ReactServerComponentPage-react-component-0") %>
44
-
45
- <h1>React Server Component with SSR</h1>
46
- ```
41
+ ```erb
42
+ # app/views/pages/react_server_component_ssr.html.erb
43
+ <%= stream_react_component("ReactServerComponentPage",
44
+ id: "ReactServerComponentPage-react-component-0") %>
45
+
46
+ <h1>React Server Component with SSR</h1>
47
+ ```
47
48
 
48
49
  2. Ensure our controller includes `ReactOnRailsPro::Stream` and use the `stream_view_containing_react_components` helper to render the view:
49
50
 
50
- ```ruby
51
- # app/controllers/pages_controller.rb
52
- class PagesController < ApplicationController
53
- include ReactOnRailsPro::Stream
54
-
55
- def react_server_component_ssr
56
- stream_view_containing_react_components(template: "pages/react_server_component_ssr")
57
- end
58
- end
59
- ```
51
+ ```ruby
52
+ # app/controllers/pages_controller.rb
53
+ class PagesController < ApplicationController
54
+ include ReactOnRailsPro::Stream
55
+
56
+ def react_server_component_ssr
57
+ stream_view_containing_react_components(template: "pages/react_server_component_ssr")
58
+ end
59
+ end
60
+ ```
60
61
 
61
62
  3. Add the route to `config/routes.rb`:
62
63
 
63
- ```ruby
64
- # config/routes.rb
65
- get "/react_server_component_ssr", to: "pages#react_server_component_ssr"
66
- ```
64
+ ```ruby
65
+ # config/routes.rb
66
+ get "/react_server_component_ssr", to: "pages#react_server_component_ssr"
67
+ ```
67
68
 
68
69
  Now, when you visit the page, you should see the entire React Server Component page rendered in the browser. And if you viewed the page source, you should see the HTML being streamed to the browser.
69
70