react_router_rails_spa 0.1.1 → 0.1.2
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/.rubocop.yml +215 -2
- data/README.md +38 -39
- data/documents/introduction.md +92 -89
- data/lib/react_router_rails_spa/generators/templates/react.rake +6 -6
- data/lib/react_router_rails_spa/generators/templates/welcome/welcome.tsx +2 -2
- data/lib/react_router_rails_spa/version.rb +1 -1
- metadata +4 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 95855ba78c935cea1d1c666a75769bf23dca4b3593b6837f29ceb2d97c3259b2
|
4
|
+
data.tar.gz: b4f53bab792448b9a8a1f7df9d6b02a37680425ab51fcdde40cc90c3b3d7dc50
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 653a9a898765b134d1154e33169be82cd31d8143c775da3216e2106b7dad3b705a26d4fb94f07e30e200e478fd7d1398ae0308c883919e8a704d7a989ef53c6c
|
7
|
+
data.tar.gz: 813669a3b2801ddfad534b8ca1135f3f7dae018b8329f5bb112ced8fdf67d5367203c23f6f229b4b277ae645c6434e1e75c58f80cfbbb06c560fb4dea40133c9
|
data/.rubocop.yml
CHANGED
@@ -1,8 +1,221 @@
|
|
1
1
|
AllCops:
|
2
2
|
TargetRubyVersion: 3.0
|
3
|
+
NewCops: enable
|
3
4
|
|
5
|
+
# Below taken from rubocop-rails-omakase
|
6
|
+
|
7
|
+
# Align `when` with `end`.
|
8
|
+
Layout/CaseIndentation:
|
9
|
+
Enabled: true
|
10
|
+
EnforcedStyle: end
|
11
|
+
|
12
|
+
# Align comments with method definitions.
|
13
|
+
Layout/CommentIndentation:
|
14
|
+
Enabled: true
|
15
|
+
|
16
|
+
Layout/ElseAlignment:
|
17
|
+
Enabled: true
|
18
|
+
|
19
|
+
Layout/EmptyLineAfterMagicComment:
|
20
|
+
Enabled: true
|
21
|
+
|
22
|
+
Layout/EmptyLinesAroundBlockBody:
|
23
|
+
Enabled: true
|
24
|
+
|
25
|
+
# In a regular class definition, no empty lines around the body.
|
26
|
+
Layout/EmptyLinesAroundClassBody:
|
27
|
+
Enabled: true
|
28
|
+
|
29
|
+
# In a regular method definition, no empty lines around the body.
|
30
|
+
Layout/EmptyLinesAroundMethodBody:
|
31
|
+
Enabled: true
|
32
|
+
|
33
|
+
# In a regular module definition, no empty lines around the body.
|
34
|
+
Layout/EmptyLinesAroundModuleBody:
|
35
|
+
Enabled: true
|
36
|
+
|
37
|
+
# Align `end` with the matching keyword or starting expression except for
|
38
|
+
# assignments, where it should be aligned with the LHS.
|
39
|
+
Layout/EndAlignment:
|
40
|
+
Enabled: true
|
41
|
+
EnforcedStyleAlignWith: variable
|
42
|
+
|
43
|
+
# Method definitions after `private` or `protected` isolated calls need one
|
44
|
+
# extra level of indentation.
|
45
|
+
#
|
46
|
+
# We break this rule in context, though, e.g. for private-only concerns,
|
47
|
+
# so we leave it disabled.
|
48
|
+
Layout/IndentationConsistency:
|
49
|
+
Enabled: false
|
50
|
+
EnforcedStyle: indented_internal_methods
|
51
|
+
|
52
|
+
# Detect hard tabs, no hard tabs.
|
53
|
+
Layout/IndentationStyle:
|
54
|
+
Enabled: true
|
55
|
+
|
56
|
+
# Two spaces, no tabs (for indentation).
|
57
|
+
#
|
58
|
+
# Doesn't behave properly with private-only concerns, so it's disabled.
|
59
|
+
Layout/IndentationWidth:
|
60
|
+
Enabled: false
|
61
|
+
|
62
|
+
Layout/LeadingCommentSpace:
|
63
|
+
Enabled: true
|
64
|
+
|
65
|
+
Layout/SpaceAfterColon:
|
66
|
+
Enabled: true
|
67
|
+
|
68
|
+
Layout/SpaceAfterComma:
|
69
|
+
Enabled: true
|
70
|
+
|
71
|
+
Layout/SpaceAroundEqualsInParameterDefault:
|
72
|
+
Enabled: true
|
73
|
+
|
74
|
+
Layout/SpaceAroundKeyword:
|
75
|
+
Enabled: true
|
76
|
+
|
77
|
+
# Use `foo {}` not `foo{}`.
|
78
|
+
Layout/SpaceBeforeBlockBraces:
|
79
|
+
Enabled: true
|
80
|
+
|
81
|
+
Layout/SpaceBeforeComma:
|
82
|
+
Enabled: true
|
83
|
+
|
84
|
+
Layout/SpaceBeforeFirstArg:
|
85
|
+
Enabled: true
|
86
|
+
|
87
|
+
# Use `->(x, y) { x + y }` not `-> (x, y) { x + y }`
|
88
|
+
Layout/SpaceInLambdaLiteral:
|
89
|
+
Enabled: true
|
90
|
+
|
91
|
+
# Use `[ a, [ b, c ] ]` not `[a, [b, c]]`
|
92
|
+
# Use `[]` not `[ ]`
|
93
|
+
Layout/SpaceInsideArrayLiteralBrackets:
|
94
|
+
Enabled: true
|
95
|
+
EnforcedStyle: space
|
96
|
+
EnforcedStyleForEmptyBrackets: no_space
|
97
|
+
|
98
|
+
# Use `%w[ a b ]` not `%w[ a b ]`.
|
99
|
+
Layout/SpaceInsideArrayPercentLiteral:
|
100
|
+
Enabled: true
|
101
|
+
|
102
|
+
# Use `foo { bar }` not `foo {bar}`.
|
103
|
+
# Use `foo { }` not `foo {}`.
|
104
|
+
Layout/SpaceInsideBlockBraces:
|
105
|
+
Enabled: true
|
106
|
+
EnforcedStyleForEmptyBraces: space
|
107
|
+
|
108
|
+
# Use `{ a: 1 }` not `{a:1}`.
|
109
|
+
# Use `{}` not `{ }`.
|
110
|
+
Layout/SpaceInsideHashLiteralBraces:
|
111
|
+
Enabled: true
|
112
|
+
EnforcedStyle: space
|
113
|
+
EnforcedStyleForEmptyBraces: no_space
|
114
|
+
|
115
|
+
# Use `foo(bar)` not `foo( bar )`
|
116
|
+
Layout/SpaceInsideParens:
|
117
|
+
Enabled: true
|
118
|
+
|
119
|
+
# Requiring a space is not yet supported as of 0.59.2
|
120
|
+
# Use `%w[ foo ]` not `%w[foo]`
|
121
|
+
Layout/SpaceInsidePercentLiteralDelimiters:
|
122
|
+
Enabled: false
|
123
|
+
#EnforcedStyle: space
|
124
|
+
|
125
|
+
# Use `hash[:key]` not `hash[ :key ]`
|
126
|
+
Layout/SpaceInsideReferenceBrackets:
|
127
|
+
Enabled: true
|
128
|
+
|
129
|
+
# Blank lines should not have any spaces.
|
130
|
+
Layout/TrailingEmptyLines:
|
131
|
+
Enabled: true
|
132
|
+
|
133
|
+
# No trailing whitespace.
|
134
|
+
Layout/TrailingWhitespace:
|
135
|
+
Enabled: true
|
136
|
+
|
137
|
+
Lint/RedundantStringCoercion:
|
138
|
+
Enabled: true
|
139
|
+
|
140
|
+
# Use my_method(my_arg) not my_method( my_arg ) or my_method my_arg.
|
141
|
+
Lint/RequireParentheses:
|
142
|
+
Enabled: true
|
143
|
+
|
144
|
+
Lint/UriEscapeUnescape:
|
145
|
+
Enabled: true
|
146
|
+
|
147
|
+
# We generally prefer &&/|| but like low-precedence and/or in context
|
148
|
+
Style/AndOr:
|
149
|
+
Enabled: false
|
150
|
+
|
151
|
+
# Prefer Foo.method over Foo::method
|
152
|
+
Style/ColonMethodCall:
|
153
|
+
Enabled: true
|
154
|
+
|
155
|
+
Style/DefWithParentheses:
|
156
|
+
Enabled: true
|
157
|
+
|
158
|
+
# Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }.
|
159
|
+
Style/HashSyntax:
|
160
|
+
Enabled: true
|
161
|
+
EnforcedShorthandSyntax: either
|
162
|
+
|
163
|
+
# Defining a method with parameters needs parentheses.
|
164
|
+
Style/MethodDefParentheses:
|
165
|
+
Enabled: true
|
166
|
+
|
167
|
+
Style/ParenthesesAroundCondition:
|
168
|
+
Enabled: true
|
169
|
+
|
170
|
+
Style/PercentLiteralDelimiters:
|
171
|
+
Enabled: true
|
172
|
+
PreferredDelimiters:
|
173
|
+
default: "()"
|
174
|
+
"%i": "[]"
|
175
|
+
"%I": "[]"
|
176
|
+
"%r": "{}"
|
177
|
+
"%w": "[]"
|
178
|
+
"%W": "[]"
|
179
|
+
|
180
|
+
# Use quotes for string literals when they are enough.
|
181
|
+
Style/RedundantPercentQ:
|
182
|
+
Enabled: false
|
183
|
+
|
184
|
+
Style/RedundantReturn:
|
185
|
+
Enabled: true
|
186
|
+
AllowMultipleReturnValues: true
|
187
|
+
|
188
|
+
Style/Semicolon:
|
189
|
+
Enabled: true
|
190
|
+
AllowAsExpressionSeparator: true
|
191
|
+
|
192
|
+
Style/StabbyLambdaParentheses:
|
193
|
+
Enabled: true
|
194
|
+
|
195
|
+
# Use `"foo"` not `'foo'` unless escaping is required
|
4
196
|
Style/StringLiterals:
|
197
|
+
Enabled: true
|
5
198
|
EnforcedStyle: double_quotes
|
6
199
|
|
7
|
-
Style/
|
8
|
-
|
200
|
+
Style/TrailingCommaInArrayLiteral:
|
201
|
+
Enabled: true
|
202
|
+
|
203
|
+
Style/TrailingCommaInHashLiteral:
|
204
|
+
Enabled: true
|
205
|
+
|
206
|
+
# Other cops
|
207
|
+
|
208
|
+
Style/Documentation:
|
209
|
+
Enabled: false
|
210
|
+
|
211
|
+
Style/FrozenStringLiteralComment:
|
212
|
+
Enabled: false
|
213
|
+
|
214
|
+
Metrics/BlockLength:
|
215
|
+
Enabled: true
|
216
|
+
Exclude:
|
217
|
+
- "**/*.gemspec"
|
218
|
+
- "**/*.rake"
|
219
|
+
|
220
|
+
Gemspec/RequireMFA:
|
221
|
+
Enabled: true
|
data/README.md
CHANGED
@@ -1,40 +1,34 @@
|
|
1
1
|
## React Router SPA Framework mode integration for Ruby on Rails
|
2
2
|
|
3
3
|
The react_router_rails_spa gem integrates [React Router in SPA Framework mode](https://reactrouter.com/how-to/spa) with your Ruby on Rails application.
|
4
|
-
The React app is built as static assets in your Rails `public` folder, and so it can be deployed on your current production server with minimal, if any, configuration changes.
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
without the costs and complexities of running a separate front-end server.
|
5
|
+
The React app is built as a static SPA.
|
6
|
+
Static assets will be built in your Rails `public` folder,
|
7
|
+
and will be deployed on your current production server with minimal, if any, configuration changes.
|
10
8
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
9
|
+
With a single gem and a single command,
|
10
|
+
this gem sets up all that you need in ["Omakase"](https://dhh.dk/2012/rails-is-omakase.html)-style – An integrated client-side router,
|
11
|
+
per-route code-splitting, the loader data-fetch pattern, Rails controllers and routes –
|
12
|
+
All of this will be automatically set up for you.
|
15
13
|
|
16
|
-
|
17
|
-
[jsbundling-rails](https://github.com/rails/jsbundling-rails), [Vite Ruby](https://github.com/ElMassimo/vite_ruby)
|
18
|
-
but concerned about installing and configuring additional packages, and avoiding SPA pitfalls.
|
19
|
-
With a single gem and a single command, this gem sets up all that you need in "Omakase"-style – Rails route and controller setup, an integrated client-side router, automatic per-route code-splitting, and the loader data-fetch pattern to eliminate data-fetch waterfalls.
|
14
|
+
[Read the introduction](https://github.com/naofumi/react_router_rails_spa/blob/main/documents/introduction.md) for more background:
|
20
15
|
|
21
16
|
## Who is it for?
|
22
17
|
|
23
|
-
Consider trying out this gem if
|
18
|
+
Consider trying out this gem if any of the below apply to you.
|
24
19
|
|
25
|
-
- You want
|
26
|
-
- You
|
27
|
-
|
28
|
-
- You
|
29
|
-
|
30
|
-
- You do not want to
|
31
|
-
- You do not want to incur the additional costs, complexity, and authentication concerns that are inherent when dealing with multiple servers.
|
20
|
+
- You want an out-of-the-box solution.
|
21
|
+
- You do not enjoy installing React, React Router, Tailwind, configuring code-splitting, and deciding the data-loading scheme that you will use throughout your application. You want ["Omakase"](https://dhh.dk/2012/rails-is-omakase.html) on the front-end as well as your Rails back-end.
|
22
|
+
- You do not need SEO, at least not for the React pages.
|
23
|
+
- You can always use ERB views for the pages that need SEO.
|
24
|
+
- You are tired of managing multiple servers for your frontend and your backend.
|
25
|
+
- You do not want to incur the additional costs, complexity, and authentication concerns that are inherent when dealing with multiple servers, for no concrete benefit.
|
32
26
|
- You want to simply deploy your React frontend as static assets on a single server, inside your Ruby on Rails `public` folder.
|
33
27
|
- You have many pages, and you want to reduce the initial JavaScript payload size by using automatic code-splitting and lazy-loading, but without sacrificing performance due to request waterfalls.
|
34
28
|
|
35
29
|
## Installation
|
36
30
|
|
37
|
-
|
31
|
+
We assume that you already have an existing Ruby on Rails application.
|
38
32
|
|
39
33
|
Add this line to your application's Gemfile:
|
40
34
|
|
@@ -46,48 +40,52 @@ Then, run:
|
|
46
40
|
|
47
41
|
```shell
|
48
42
|
bundle install
|
43
|
+
```
|
44
|
+
|
45
|
+
followed by:
|
46
|
+
|
47
|
+
```shell
|
49
48
|
bin/rails generate react_router_rails_spa:install
|
50
49
|
```
|
51
50
|
|
52
51
|
This will create a new directory called `frontend` inside the project root.
|
53
|
-
|
54
|
-
The endpoint will be handled by `ReactController#show`.
|
52
|
+
This is where your React application is.
|
55
53
|
|
56
|
-
|
57
|
-
|
58
|
-
As part of the integration, we provide utilities for using the robust CSRF protection built into Ruby on Rails from your React application.
|
54
|
+
It will also create a React bootstrap endpoint in your Rails routes for all paths starting with `/react`.
|
55
|
+
The endpoint will be handled by `ReactController#show`.
|
59
56
|
|
60
57
|
## Running the React Router development server
|
61
58
|
|
62
|
-
React Router is built with Vite and uses the Vite development server to provide a Hot Module Replacement (HMR) capability
|
63
|
-
However, the development server is not used in production and will not widely represent the application's behavior in production.
|
64
|
-
We therefore strongly recommend that you build the React Router assets into the `public` folder and preview it before deploying into production.
|
59
|
+
React Router is built with Vite and uses the Vite development server to provide a Hot Module Replacement (HMR) capability.
|
65
60
|
|
66
|
-
Start the Vite development server with
|
61
|
+
Start the Vite development server with the following command
|
62
|
+
(we assume that the Ruby on Rails server is already running (with either the `bin/rails s` or the `bin/dev` command).
|
67
63
|
|
68
64
|
```shell
|
69
65
|
bin/rails react_router:dev
|
70
66
|
```
|
71
67
|
|
72
|
-
|
68
|
+
Note that the development server will not fully represent the application's behavior in production.
|
69
|
+
In particular, the development server is only partially integrated with Rails.
|
70
|
+
We therefore strongly recommend that you build the React Router assets into the `public` folder and preview it before deploying into production.
|
73
71
|
|
74
72
|
To preview the production build, run the following command.
|
75
73
|
```shell
|
76
74
|
bin/rails react_router:preview
|
77
75
|
```
|
78
76
|
|
79
|
-
This will build the React Router application into the Rails `public` folder
|
80
|
-
The
|
77
|
+
This will build the React Router application into the Rails `public` folder.
|
78
|
+
The React Router application will be available from the Rails development server
|
79
|
+
(puma) at `http://localhost:3000/react`.
|
80
|
+
This preview will be representative of the production app's behavior with Rails integration.
|
81
81
|
|
82
82
|
## Deployment
|
83
83
|
|
84
|
-
This gem integrates with the Ruby on Rails Asset pipeline.
|
85
|
-
|
86
|
-
The React Router application is automatically built whenever `bin/rails assets:precompile` is run
|
87
|
-
and therefore no changes should be required since your Ruby on Rails application should already do this.
|
84
|
+
This gem integrates with the Ruby on Rails Asset pipeline, and the React application is automatically built whenever `bin/rails assets:precompile` is run.
|
85
|
+
No changes are required on your deployment script, since your Ruby on Rails application should run this command.
|
88
86
|
|
89
87
|
If your deployment pipeline does not already install Node (for example, you have a "no-build" deployment for Rails),
|
90
|
-
then install it in your CI/CD environment since building the React Router application will require it.
|
88
|
+
then install it in your CI/CD environment since building the React Router application will require it.
|
91
89
|
|
92
90
|
## Background
|
93
91
|
|
@@ -128,6 +126,7 @@ Importantly, API mode implies a stateless API server that does not support cooki
|
|
128
126
|
It removes the middleware for cookie handling and also for CSRF protection.
|
129
127
|
|
130
128
|
To fully benefit from hosting your React app inside Rails' `public` folder, we recommend that you avoid API-mode and instead use cookies for authentication.
|
129
|
+
|
131
130
|
If you want to convert your API-mode application to use cookies, make sure to also restore CSRF features.
|
132
131
|
Otherwise, your app will be vulnerable to CSRF attacks.
|
133
132
|
|
data/documents/introduction.md
CHANGED
@@ -2,42 +2,63 @@
|
|
2
2
|
|
3
3
|
## TL;DR;
|
4
4
|
|
5
|
-
|
5
|
+
Integrating React onto a Ruby on Rails application is unnecessarily challenging.
|
6
|
+
We have to install jsbundling-rails, install multiple packages, configure propshaft, create an ERB endpoint, etc.
|
6
7
|
|
7
|
-
|
8
|
+
Why can't we just run `rails new`,
|
9
|
+
install a single gem, and instantly have a state-of-the-art React setup with client-side routing,
|
10
|
+
code-splitting, and effective data-loading patterns built-in?
|
11
|
+
|
12
|
+
This article introduces the [react_router_rails_spa gem](https://github.com/naofumi/react_router_rails_spa),
|
13
|
+
which integrates a React Router SPA framework application into your existing Ruby on Rails project.
|
14
|
+
With just a few commands, you will have a fully functioning scaffold on which to build your React app.
|
15
|
+
|
16
|
+
To jump to the installation steps, go to the ["Using the gem" section](#using-the-gem).
|
8
17
|
|
9
18
|
## Who is this for?
|
10
19
|
|
11
|
-
|
20
|
+
If any of the following describes yourself, then this gem might be for you.
|
12
21
|
|
13
|
-
### You want to create
|
22
|
+
### You want to create an SPA with a React frontend and a Rails backend.
|
14
23
|
|
15
|
-
* You want a simple setup that is ["omakase"](https://dhh.dk/2012/rails-is-omakase.html). You don't want to install packages and configure them on the React side.
|
16
|
-
* You want something that is easy to deploy, and cost-effective. You don't want to
|
24
|
+
* You want a simple setup that is ["omakase"](https://dhh.dk/2012/rails-is-omakase.html). You don't want to install packages and configure them on the React side. On the Rails side, you don't want to manually add routes and controllers. Everything should be a single gem and a single command.
|
25
|
+
* You want something that is easy to deploy, and cost-effective. You don't want to pay for an extra server that you don't strictly need.
|
17
26
|
* You don't need SEO for the React pages.
|
18
27
|
* If SEO is necessary, you can just serve ERB pages or static HTML files.
|
19
28
|
|
20
|
-
### You
|
29
|
+
### You tried Next.js, but you did not really need SSR nor RSCs. You were only interested in Next.js because you thought it was easy.
|
21
30
|
|
22
|
-
* You're only using Next.js because you thought it was ["omakase"](https://dhh.dk/2012/rails-is-omakase.html)
|
23
|
-
*
|
31
|
+
* You're only using Next.js because you thought it was ["omakase"](https://dhh.dk/2012/rails-is-omakase.html) and simple to set up.
|
32
|
+
* After a while, you've found that integrating the Next.js server and the Rails server is harder than you bargained for. You've had headaches around authentication schemes, cross-domains, subdomains, CORS settings, samesite cookies, CSRF mitigation, and reverse proxies, etc. You've realized that running separate frontend and backend servers is hard.
|
33
|
+
* You are not happy with the extra money you have to pay to Next.js.
|
24
34
|
|
25
|
-
The react_router_rails_spa gem
|
26
|
-
|
35
|
+
The [react_router_rails_spa gem](https://github.com/naofumi/react_router_rails_spa) gives you the simplicity of an opinionated SPA framework,
|
36
|
+
without the complexity of a mulit-server setup.
|
27
37
|
|
28
|
-
### You want to use React
|
38
|
+
### You want to use cutting-edge React
|
29
39
|
|
30
|
-
* You
|
31
|
-
* You
|
40
|
+
* You want to use cutting-edge React capabilities like code-splitting, loader-based data fetching and more, but you're not sure how to [set this up from scratch](https://react.dev/learn/build-a-react-app-from-scratch).
|
41
|
+
* You don't want to create a slow, bloated, legacy React app.
|
32
42
|
|
33
43
|
## Who is this NOT for?
|
34
44
|
|
35
45
|
### You want to embedd some React components on top of your ERB-rendered pages
|
36
46
|
|
37
|
-
This is [
|
47
|
+
This is [how React was originally used](https://react.dev/learn/add-react-to-an-existing-project#using-react-for-a-part-of-your-existing-page).
|
48
|
+
React was built for this, and it's generally much simpler to set up than a multi-page SPA.
|
49
|
+
|
50
|
+
The current gem does not help you with this.
|
38
51
|
If you wish to take this approach,
|
39
52
|
you can build your own system or use Gems like [react-rails](https://github.com/reactjs/react-rails) and [turbo-mount](https://github.com/skryukov/turbo-mount).
|
40
|
-
Turbo Mount uses Stimulus to mount components, and is
|
53
|
+
Turbo Mount uses Stimulus to mount components, and is more robust if you are using Hotwire in your ERB views.
|
54
|
+
|
55
|
+
## How does this compare to [...]?
|
56
|
+
|
57
|
+
Compared to gems like [React on Rails](https://github.com/shakacode/react_on_rails) or [Intertia Rails](https://inertia-rails.dev/),
|
58
|
+
the current gem is just an installer and does virtually nothing to modify or add features to React Router.
|
59
|
+
This is a great advantage and ensures that frontend developers will feel right at home.
|
60
|
+
|
61
|
+
It also means that you can easily understand how it works. You can customize accordingly.
|
41
62
|
|
42
63
|
## Background
|
43
64
|
|
@@ -50,9 +71,9 @@ Importantly, and often lost in the public discourse, they were **NOT** recommend
|
|
50
71
|
Instead, they were advocating for creating SPAs with [**SPA** frameworks](https://react.dev/blog/2025/02/14/sunsetting-create-react-app#how-to-migrate-to-a-framework)
|
51
72
|
that could be deployed on a CDN, a static hosting service, or the `public` folder of a Ruby on Rails application.
|
52
73
|
|
53
|
-
In the following,
|
74
|
+
In the following, we will call SPAs built with an SPA framework, **"Modern React SPAs"**
|
54
75
|
to highlight that this is the current officially recommended approach.
|
55
|
-
To contrast,
|
76
|
+
To contrast, we will call the ones that the React team is actively discouraging, **"Legacy React SPAs"**.
|
56
77
|
|
57
78
|
> **"I have no interest nor use for SSR!
|
58
79
|
I don't need SEO.
|
@@ -79,7 +100,7 @@ to tell us
|
|
79
100
|
that we should not simply replace the deprecated Create React App with a newer but nonetheless still architecturally Legacy SPA.
|
80
101
|
Instead, they strongly urge us to embrace Modern React SPAs and avoid these issues.
|
81
102
|
|
82
|
-
|
103
|
+
We should note that Vite is essentially a bundler and a development server, with a plugin system that allows us to easily install various packages.
|
83
104
|
It is agnostic to the Legacy vs. Modern SPA debate.
|
84
105
|
You can build a Legacy SPA using Vite, and you can also create a Modern SPA.
|
85
106
|
Vite does not care either way, and the installer command `npm create vite@latest` gives you both templates.
|
@@ -93,9 +114,9 @@ The above solution is a Legacy SPA and will suffer from the same legacy issues.
|
|
93
114
|
Instead, the React team is recommending that you integrate a Modern SPA framework using ...
|
94
115
|
|
95
116
|
Well, actually, they don't have a concrete recommendation yet for Rails.
|
96
|
-
As far as
|
117
|
+
As far as we know, nothing currently exists to easily integrate a Modern SPA with Rails.
|
97
118
|
|
98
|
-
|
119
|
+
We hope to address this with this [react_router_rails_spa gem](https://github.com/naofumi/react_router_rails_spa).
|
99
120
|
|
100
121
|
## Why we need a different approach for Rails integration
|
101
122
|
|
@@ -126,51 +147,41 @@ This is how the react_router_rails_spa gem works.
|
|
126
147
|
It is important to note that the [react_router_rails_spa gem](https://github.com/naofumi/react_router_rails_spa) does nothing more than a stock React Router installation with some custom configuration,
|
127
148
|
paired with the generation of a single Rails controller.
|
128
149
|
|
129
|
-
There is very little custom code, and
|
130
|
-
|
131
|
-
|
150
|
+
**There is very little custom code, and this is actually a huge advantage**.
|
151
|
+
This is a very thin wrapper around the official React Router installer and resilient against future changes.
|
152
|
+
If you wish, you can easily update and customize your NPM packages independently of this gem. The generated code is also heavily commented
|
153
|
+
to help you understand the internals for yourself.
|
132
154
|
|
133
|
-
|
134
|
-
This will build static files that can be served from any static hosting provider, including the `public` folder in Rails.
|
155
|
+
### React Router SPA framework mode
|
135
156
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
we use a dedicated Rails controller to add Rails-generated HTTP headers and cookies
|
140
|
-
and serve the file as the response body.
|
141
|
-
|
142
|
-
After building, these static files will be transferred to the Rails `public` folder from which they can be deployed like any other static asset.
|
157
|
+
We install and use React Router in SPA framework mode, [configured to generate an SPA build](https://reactrouter.com/how-to/spa).
|
158
|
+
It is a true SPA and will build static files that can be served from any static hosting provider.
|
159
|
+
These are transferred to the `public` folder in Rails for deployment.
|
143
160
|
|
144
|
-
|
161
|
+
The command for building the react application is integrated into the `rake assets:precompile` command.
|
162
|
+
Therefore, you do not need any additional configuration in your CI/CD scripts.
|
145
163
|
|
146
|
-
|
147
|
-
The body of the response is the exact contents of the React Router-generated `index.html` file, but
|
148
|
-
by passing it through the `ReactController`,
|
149
|
-
we can customize the headers and add session-specific information as cookies.
|
150
|
-
|
151
|
-
For example, `ReactController`
|
152
|
-
includes the `ReactRouterRailsSpa::CsrfCookieEnabled` module
|
153
|
-
which sends session-specific CSRF tokens via cookies to integrate Rails'
|
154
|
-
CSRF protection with React.
|
164
|
+
### Integration with Ruby on Rails through cookies
|
155
165
|
|
156
|
-
|
166
|
+
Previously, you would integrate Ruby on Rails using a bootstrap HTML template generated by Rails using ERB templates.
|
167
|
+
This allowed you, for example, to embed CSRF-mitigation token tags.
|
168
|
+
As mentioned above, this is incompatible with Modern SPA frameworks.
|
157
169
|
|
158
|
-
|
170
|
+
Otherwise,
|
171
|
+
you could build your SPA independently of Rails
|
172
|
+
and send extra requests from the browser to retrieve the CSRF token and other integration information.
|
173
|
+
This requires otherwise unnecessary network requests.
|
159
174
|
|
160
|
-
|
161
|
-
to serve assets from the `public` folder,
|
162
|
-
and this sets the HTTP caching headers aggressively to allow extensive caching for long periods of time.
|
163
|
-
While this is great for JavaScript, CSS and image assets,
|
164
|
-
this is usually undesirable for HTML files since we cannot attach cache-busting digests to them.
|
165
|
-
Serving the bootstrap HTML template through `ReactController` allows us
|
166
|
-
to easily change the cache headers to values that are suitable for HTML responses.
|
167
|
-
Currently, the [react_router_rails_spa gem](https://github.com/naofumi/react_router_rails_spa) uses the same cache headers as other ERB files,
|
168
|
-
but this can be customized in the controller.
|
175
|
+
Neither approach is ideal.
|
169
176
|
|
170
|
-
|
177
|
+
Instead,
|
178
|
+
the [react_router_rails_spa gem](https://github.com/naofumi/react_router_rails_spa) sends Rails-generated cookies and/or headers alongside the SPA framework-generated bootstrap HTML file.
|
179
|
+
It give you the best of both worlds.
|
171
180
|
|
172
|
-
|
173
|
-
|
181
|
+
For example, the `ReactRouterRailsSpa::CsrfCookieEnabled` module
|
182
|
+
sends session-specific CSRF tokens via cookies to integrate Rails'
|
183
|
+
CSRF protection with React.
|
184
|
+
The SPA framework-generated HTML is untouched.
|
174
185
|
|
175
186
|
### Rake files for automation
|
176
187
|
|
@@ -191,31 +202,13 @@ All other paths are handled by Rails.
|
|
191
202
|
The current gem adds minor configurations for this.
|
192
203
|
If you want a different setup, you can change the configurations.
|
193
204
|
|
194
|
-
Note that configurations for the Vite development server are tricky.
|
195
|
-
We have provided settings
|
196
|
-
to compensate for the fact that the development server runs on port 5173 while the Rails application runs on port 3000.
|
197
|
-
However, this will not allow you to test integration between Rails and React.
|
198
|
-
|
199
|
-
* The React app runs on port 5173 while the ERB files are on port 3000. Links between the two will not work on the development server, even if they are fine in production.
|
200
|
-
* The React app running on the development server will not bootstrap from the Ruby on Rails endpoint on the `ReactController#show` action. Instead, the development server will directly serve the React Router generated bootstrap HTML. This means that the bootstrap file will not contain Rails integrations. This is only an issue on the development server and not in production.
|
201
|
-
|
202
|
-
As a solution, you can use the development server with HMR for small fixes, but for larger changes,
|
203
|
-
you will need to use a "preview" build.
|
204
|
-
You should also always check with a "preview" build before deploying to production.
|
205
|
-
|
206
|
-
We provide a rake task for building a "preview".
|
207
|
-
|
208
|
-
```shell
|
209
|
-
bin/rails react_router:preview
|
210
|
-
```
|
211
|
-
|
212
205
|
## Demo and Source code
|
213
206
|
|
214
|
-
*
|
215
|
-
* In the demo application,
|
207
|
+
* We have a [demo application running on Kamal on a VPS server](https://rrrails.castle104.com/react/). It has simple, session-based authentication and basic CRUD. Mutations are secured by integration with Rails CSRF protection.
|
208
|
+
* In the demo application, we have intentionally added a 0.5 to 1.5-second random delay on all server requests. Even the most bloated and inefficient web technologies will look great on a high-performance device with a fast network. Unless your demo intentionally simulates non-ideal situations, it is meaningless.
|
216
209
|
* The source-code for this demo application is [available on GitHub](https://github.com/naofumi/react-router-vite-rails).
|
217
210
|
|
218
|
-
The source code is heavily commented.
|
211
|
+
The source code is heavily commented. We recommend that you read through it to understand the setup in more detail.
|
219
212
|
|
220
213
|
## Using the gem
|
221
214
|
|
@@ -227,8 +220,16 @@ This gem works with a pre-existing installation of Rails. Create a new Rails app
|
|
227
220
|
rails new [project name]
|
228
221
|
```
|
229
222
|
|
230
|
-
Note that this gem works even with a no-build Rails setup (which is the Rails default)
|
231
|
-
|
223
|
+
Note that this gem works even with a no-build Rails setup (which is the Rails default).
|
224
|
+
However, you will need Node.js in your CI/CD for deployment.
|
225
|
+
If you are unsure how to do this,
|
226
|
+
we recommend that you generate a new Rails application with jsbundling-rails pre-installed.
|
227
|
+
This will create a ready-made Dockerfile that installs Node.js.
|
228
|
+
(This gem won't use esbuild. We're only doing this for Node.js.)
|
229
|
+
|
230
|
+
```shell
|
231
|
+
rails new [project name] --javascript esbuild
|
232
|
+
```
|
232
233
|
|
233
234
|
### Install the `react_router_rails_spa` gem
|
234
235
|
|
@@ -257,7 +258,7 @@ and generate the routes and all the necessary files and configurations.
|
|
257
258
|
|
258
259
|
### Run the development server
|
259
260
|
|
260
|
-
Run the following command to start the development server
|
261
|
+
Run the following command to start the frontend development server with HMR (Hot Module Replacement).
|
261
262
|
|
262
263
|
```shell
|
263
264
|
bin/rails react_router:dev
|
@@ -265,7 +266,11 @@ bin/rails react_router:dev
|
|
265
266
|
|
266
267
|
Point your browser to http://localhost:5173/react/ to see the welcome page.
|
267
268
|
|
268
|
-
|
269
|
+
Note that the frontend development server will not truly represent
|
270
|
+
how the React application and Rails server interact in production.
|
271
|
+
It is important to test with the following preview command before deploying.
|
272
|
+
|
273
|
+
### Preview and build the React Router application
|
269
274
|
|
270
275
|
Run the following command to build the React Router application
|
271
276
|
and store the static files into the Rails `public` directory.
|
@@ -274,17 +279,15 @@ and store the static files into the Rails `public` directory.
|
|
274
279
|
bin/rails react_router:build
|
275
280
|
```
|
276
281
|
|
277
|
-
|
278
|
-
|
279
|
-
Point your browser to http://localhost:3000/react/ to see the welcome page.
|
280
|
-
|
281
|
-
This command is also aliased as `preview`.
|
282
|
+
This command is also aliased as:
|
282
283
|
|
283
284
|
```shell
|
284
285
|
bin/rails react_router:preview
|
285
286
|
```
|
286
287
|
|
287
|
-
|
288
|
+
Start your Rails application if it is not already running and point your browser to http://localhost:3000/react/ to see the welcome page.
|
289
|
+
|
290
|
+
### Read the code
|
288
291
|
|
289
|
-
|
292
|
+
We have added numerous comments to the code generated by this gem. Please read it to understand how the integration works.
|
290
293
|
|
@@ -18,7 +18,7 @@ namespace :react_router do
|
|
18
18
|
#
|
19
19
|
# bin/rails react_router:dev
|
20
20
|
desc "Start React Router Development Server with Hot Module Reloading"
|
21
|
-
task dev: [:npm_install] do
|
21
|
+
task dev: [ :npm_install ] do
|
22
22
|
puts "Starting React Router v7 app development server..."
|
23
23
|
Dir.chdir("#{Dir.pwd}/frontend") do
|
24
24
|
system("npm", "run", "dev")
|
@@ -27,7 +27,7 @@ namespace :react_router do
|
|
27
27
|
|
28
28
|
# bin/rails react_router:typecheck
|
29
29
|
desc "Check Typescript for the React Router App"
|
30
|
-
task typecheck: [:npm_install] do
|
30
|
+
task typecheck: [ :npm_install ] do
|
31
31
|
puts "Check Typescript for React Router v7 app..."
|
32
32
|
Dir.chdir("#{Dir.pwd}/frontend") do
|
33
33
|
system("npm", "run", "typecheck")
|
@@ -42,7 +42,7 @@ namespace :react_router do
|
|
42
42
|
#
|
43
43
|
# bin/rails react_router:build
|
44
44
|
desc "Build React Router App and move to the public folder"
|
45
|
-
task build: [:npm_install] do
|
45
|
+
task build: [ :npm_install ] do
|
46
46
|
puts "Building React Router v7 app..."
|
47
47
|
`cd frontend && npm run build`
|
48
48
|
|
@@ -59,7 +59,7 @@ namespace :react_router do
|
|
59
59
|
# This is identical to running bin/rails react_router: build
|
60
60
|
# and is provided solely to align better with intent.
|
61
61
|
desc "Preview your React Router App from the Rails development server (typically port 3000)"
|
62
|
-
task preview: [:build]
|
62
|
+
task preview: [ :build ]
|
63
63
|
|
64
64
|
# Run bin/rails react_router:clobber to remove the build files.
|
65
65
|
# Running bin/rails assets:clobber will also run this task.
|
@@ -75,5 +75,5 @@ end
|
|
75
75
|
# This means that any normal Rails deployment script which
|
76
76
|
# contains rake assets:precompile will also build the
|
77
77
|
# React Router app automatically.
|
78
|
-
Rake::Task["assets:precompile"].enhance(["react_router:build"])
|
79
|
-
Rake::Task["assets:clobber"].enhance(["react_router:clobber"])
|
78
|
+
Rake::Task["assets:precompile"].enhance([ "react_router:build" ])
|
79
|
+
Rake::Task["assets:clobber"].enhance([ "react_router:clobber" ])
|
@@ -61,7 +61,7 @@ const resources = [
|
|
61
61
|
text: "Read the README for the react_router_rails_spa gem",
|
62
62
|
},
|
63
63
|
{
|
64
|
-
href: "https://github.com/naofumi/react_router_rails_spa/
|
64
|
+
href: "https://github.com/naofumi/react_router_rails_spa/blob/main/documents/introduction.md",
|
65
65
|
text: "Read the Introduction for the react_router_rails_spa gem",
|
66
66
|
},
|
67
67
|
{
|
@@ -69,7 +69,7 @@ const resources = [
|
|
69
69
|
text: "Read the React Router Docs",
|
70
70
|
},
|
71
71
|
{
|
72
|
-
href: "https://
|
72
|
+
href: "https://rubyonrails.org/",
|
73
73
|
text: "Read the Ruby on Rails Docs",
|
74
74
|
},
|
75
75
|
];
|
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: react_router_rails_spa
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Naofumi Kagami
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-04-
|
12
|
-
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: rails
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - ">="
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: 8.0.2
|
20
|
-
type: :development
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - ">="
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: 8.0.2
|
11
|
+
date: 2025-04-22 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
27
13
|
description:
|
28
14
|
email:
|
29
15
|
- naofumi@mac.com
|
@@ -67,6 +53,7 @@ metadata:
|
|
67
53
|
homepage_uri: https://github.com/naofumi/react_router_rails_spa
|
68
54
|
source_code_uri: https://github.com/naofumi/react_router_rails_spa
|
69
55
|
changelog_uri: https://github.com/naofumi/react_router_rails_spa
|
56
|
+
rubygems_mfa_required: 'true'
|
70
57
|
post_install_message:
|
71
58
|
rdoc_options: []
|
72
59
|
require_paths:
|