react_router_rails_spa 0.1.0 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 261fa2216af64ecdaff4a8d74850b7ccebd05f4c3d0fbd8af986fd0658faf873
4
- data.tar.gz: 262c1899faf8d7b574d7c6523fb864139c640a1faf274c581f07139d68ae68bd
3
+ metadata.gz: 95855ba78c935cea1d1c666a75769bf23dca4b3593b6837f29ceb2d97c3259b2
4
+ data.tar.gz: b4f53bab792448b9a8a1f7df9d6b02a37680425ab51fcdde40cc90c3b3d7dc50
5
5
  SHA512:
6
- metadata.gz: dec2a2a6262c9b3cbff0fa5f8f301d55cef2433b0f2b29350a610874240b735ca7d6b265f271df8dbd91e33cd922c7a41c038417ed22dd3f195a3dea77b671ac
7
- data.tar.gz: 6419659c7dd675ea3e531efc15a6dfaef38597db12d420f84eb9ac61c0bb088121998c4e333bf2b2801d552a34010f0d802ca3aa494fe44cfc0534726e83791c
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/StringLiteralsInInterpolation:
8
- EnforcedStyle: double_quotes
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,38 +1,138 @@
1
- # ReactRouterRails
1
+ ## React Router SPA Framework mode integration for Ruby on Rails
2
2
 
3
- TODO: Delete this and the text below, and describe your gem
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
4
 
5
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/react_router_rails_spa`. To experiment with that code, run `bin/console` for an interactive prompt.
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.
8
+
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.
13
+
14
+ [Read the introduction](https://github.com/naofumi/react_router_rails_spa/blob/main/documents/introduction.md) for more background:
15
+
16
+ ## Who is it for?
17
+
18
+ Consider trying out this gem if any of the below apply to you.
19
+
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.
26
+ - You want to simply deploy your React frontend as static assets on a single server, inside your Ruby on Rails `public` folder.
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.
6
28
 
7
29
  ## Installation
8
30
 
9
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
31
+ We assume that you already have an existing Ruby on Rails application.
32
+
33
+ Add this line to your application's Gemfile:
34
+
35
+ ```ruby
36
+ gem 'react_router_rails_spa'
37
+ ```
38
+
39
+ Then, run:
40
+
41
+ ```shell
42
+ bundle install
43
+ ```
44
+
45
+ followed by:
46
+
47
+ ```shell
48
+ bin/rails generate react_router_rails_spa:install
49
+ ```
50
+
51
+ This will create a new directory called `frontend` inside the project root.
52
+ This is where your React application is.
53
+
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`.
56
+
57
+ ## Running the React Router development server
10
58
 
11
- Install the gem and add to the application's Gemfile by executing:
59
+ React Router is built with Vite and uses the Vite development server to provide a Hot Module Replacement (HMR) capability.
12
60
 
13
- ```bash
14
- bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
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).
63
+
64
+ ```shell
65
+ bin/rails react_router:dev
15
66
  ```
16
67
 
17
- If bundler is not being used to manage dependencies, install the gem by executing:
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.
18
71
 
19
- ```bash
20
- gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
72
+ To preview the production build, run the following command.
73
+ ```shell
74
+ bin/rails react_router:preview
21
75
  ```
22
76
 
23
- ## Usage
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
+
82
+ ## Deployment
83
+
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.
86
+
87
+ If your deployment pipeline does not already install Node (for example, you have a "no-build" deployment for Rails),
88
+ then install it in your CI/CD environment since building the React Router application will require it.
89
+
90
+ ## Background
91
+
92
+ Ruby on Rails has officially supported bundling of complex JavaScript frontend libraries,
93
+ first with [Webpacker](https://github.com/rails/webpacker),
94
+ and currently with [jsbundling-rails](https://github.com/rails/jsbundling-rails).
95
+ We also have [Vite Ruby](https://github.com/ElMassimo/vite_ruby) with an integrated dev server, HMR and other goodies.
96
+
97
+ However, on February 14th, 2025,
98
+ the React team announced the [official deprecation of Create React App (CRA)](https://react.dev/blog/2025/02/14/sunsetting-create-react-app).
99
+ Instead, they suggested developers should use a framework that builds single-page apps
100
+ (SPA) deployable to a CDN or a static hosting service –
101
+ In other words, SPA frameworks such as Next.js or React Router.
102
+ Importantly,
103
+ they advised
104
+ against [building a React app from scratch](https://react.dev/learn/build-a-react-app-from-scratch)
105
+ unless your app has constraints not well-served by existing SPA frameworks.
106
+
107
+ This approach poses challenges to the traditional Rails integration solutions.
108
+ They all compile the React applications to a single JavaScript file
109
+ that is then loaded from an ERB file (the React bootstrap HTML) containing helper functions such as
110
+ `javascript_include_tag` (jsbundling-rails), `javascript_pack_tag` (webpacker), or `vite_javascript_tag` (vite-rails).
111
+ However, SPA frameworks do not just compile to a single JavaScript file.
112
+ Instead,
113
+ they additionally generate their own React bootstrap HTML file which includes many framework-specific optimizations
114
+ (React Router v7 generates this using SSG from `app/root.tsx`).
115
+
116
+ This gem uses the bootstrap HTML file
117
+ generated by the SPA framework instead of an ERB file.
118
+ This is served through an ActionController endpoint,
119
+ enabling you to use cookies to send session-specific information on the first HTML load
120
+ and is used in this gem to send CSRF tokens.
24
121
 
25
- TODO: Write usage instructions here
122
+ ## Notes when using Rails in API mode
26
123
 
27
- ## Development
124
+ Ruby on Rails provides an API mode that removes many frontend features.
125
+ Importantly, API mode implies a stateless API server that does not support cookies.
126
+ It removes the middleware for cookie handling and also for CSRF protection.
28
127
 
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
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.
30
129
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
130
+ If you want to convert your API-mode application to use cookies, make sure to also restore CSRF features.
131
+ Otherwise, your app will be vulnerable to CSRF attacks.
32
132
 
33
133
  ## Contributing
34
134
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/react_router_rails_spa. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/re_rou_ra/blob/main/CODE_OF_CONDUCT.md).
135
+ Bug reports and pull requests are welcome on GitHub at https://github.com/naofumi/react_router_rails_spa. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/naofumi/react_router_rails_spa/blob/main/CODE_OF_CONDUCT.md).
36
136
 
37
137
  ## License
38
138
 
@@ -40,4 +140,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
40
140
 
41
141
  ## Code of Conduct
42
142
 
43
- Everyone interacting in the ReactRouterRails project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/react_router_rails_spa/blob/main/CODE_OF_CONDUCT.md).
143
+ Everyone interacting in the ReactRouterRailSpa project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/naofumi/react_router_rails_spa/blob/main/CODE_OF_CONDUCT.md).
@@ -0,0 +1,293 @@
1
+ # Integrating Ruby on Rails with Modern SPAs
2
+
3
+ ## TL;DR;
4
+
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.
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).
17
+
18
+ ## Who is this for?
19
+
20
+ If any of the following describes yourself, then this gem might be for you.
21
+
22
+ ### You want to create an SPA with a React frontend and a Rails backend.
23
+
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.
26
+ * You don't need SEO for the React pages.
27
+ * If SEO is necessary, you can just serve ERB pages or static HTML files.
28
+
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.
30
+
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.
34
+
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.
37
+
38
+ ### You want to use cutting-edge React
39
+
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.
42
+
43
+ ## Who is this NOT for?
44
+
45
+ ### You want to embedd some React components on top of your ERB-rendered pages
46
+
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.
51
+ If you wish to take this approach,
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).
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.
62
+
63
+ ## Background
64
+
65
+ On February 14th, 2025,
66
+ the React team published a blog post
67
+ titled [Sunsetting Create React App](https://react.dev/blog/2025/02/14/sunsetting-create-react-app). They strongly recommended
68
+ that **developers should now use an SPA framework instead**.
69
+
70
+ Importantly, and often lost in the public discourse, they were **NOT** recommending an **SSR** framework.
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)
72
+ that could be deployed on a CDN, a static hosting service, or the `public` folder of a Ruby on Rails application.
73
+
74
+ In the following, we will call SPAs built with an SPA framework, **"Modern React SPAs"**
75
+ to highlight that this is the current officially recommended approach.
76
+ To contrast, we will call the ones that the React team is actively discouraging, **"Legacy React SPAs"**.
77
+
78
+ > **"I have no interest nor use for SSR!
79
+ I don't need SEO.
80
+ > An SPA is all that I need.
81
+ > Instead of Create React App, I'll just use Vite!"**
82
+
83
+ This was the most common response to the blog post.
84
+ However IMO, it misses the point that the authors were repeatedly trying to make.
85
+
86
+ The React team is strongly recommending that **even if you only need an SPA, you should be creating a Modern SPA**.
87
+ [They carefully go through some of the features](https://react.dev/learn/build-a-react-app-from-scratch) like code-splitting
88
+ and loader-based routing
89
+ that you will need to add if you are building a state-of-the-art React SPA from scratch.
90
+ These features are often challenging and require expertise to correctly implement,
91
+ but are essential for modern React applications.
92
+
93
+ Without these features, your React SPA is a Legacy SPA.
94
+ It will suffer from the same performance issues that plagued old SPAs a decade ago –
95
+ namely huge initial bundle size, data-fetch waterfalls, flickering, and very slow load times.
96
+
97
+ In the above article,
98
+ the React team went out of its way
99
+ to tell us
100
+ that we should not simply replace the deprecated Create React App with a newer but nonetheless still architecturally Legacy SPA.
101
+ Instead, they strongly urge us to embrace Modern React SPAs and avoid these issues.
102
+
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.
104
+ It is agnostic to the Legacy vs. Modern SPA debate.
105
+ You can build a Legacy SPA using Vite, and you can also create a Modern SPA.
106
+ Vite does not care either way, and the installer command `npm create vite@latest` gives you both templates.
107
+
108
+ > **"I want to integrate my React app with a Ruby on Rails backend. I'll just add a `javascript_include_tag` to my bootstrap ERB template. That will load React just fine!"**
109
+
110
+ This is also a common response from people who prefer a no-fuss solution for Rails.
111
+ However, note that this is exactly the approach that the React team strongly discourages.
112
+ The above solution is a Legacy SPA and will suffer from the same legacy issues.
113
+
114
+ Instead, the React team is recommending that you integrate a Modern SPA framework using ...
115
+
116
+ Well, actually, they don't have a concrete recommendation yet for Rails.
117
+ As far as we know, nothing currently exists to easily integrate a Modern SPA with Rails.
118
+
119
+ We hope to address this with this [react_router_rails_spa gem](https://github.com/naofumi/react_router_rails_spa).
120
+
121
+ ## Why we need a different approach for Rails integration
122
+
123
+ Historically,
124
+ the way to integrate React with Ruby on Rails was to create an ERB endpoint that served as the initial HTML
125
+ (the bootstrap HTML) for React.
126
+ This ERB template would have either a `javascript_include_tag` (jsbundling-rails) or a
127
+ `javascript_pack_tag` (webpacker) to load the React application build artifact.
128
+ Newer gems like [Vite Rails](https://vite-ruby.netlify.app/guide/rails.html#tag-helpers-🏷) have also adopted the same approach.
129
+
130
+ However, this is exactly what the React team is discouraging, and it seems unwise to continue down this path.
131
+
132
+ The problem is that Modern SPAs build their own bootstrap HTML templates with SSG
133
+ (the first HTML that the browser loads).
134
+ Modern SPA frameworks are not just JavaScript.
135
+ Instead, the bootstrap HTML and the JavaScript are tightly integrated.
136
+
137
+ Therefore, to take advantage of Modern SPA features,
138
+ Rails has
139
+ to give up
140
+ on generating its own bootstrap HTML from an ERB template with an embedded `javascript_include_tag`.
141
+ Instead, we have to take the bootstrap HTML generated by the SPA framework, and wrap Rails around this.
142
+
143
+ This is how the react_router_rails_spa gem works.
144
+
145
+ ## Outline of how the gem works
146
+
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,
148
+ paired with the generation of a single Rails controller.
149
+
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.
154
+
155
+ ### React Router SPA framework mode
156
+
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.
160
+
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.
163
+
164
+ ### Integration with Ruby on Rails through cookies
165
+
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.
169
+
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.
174
+
175
+ Neither approach is ideal.
176
+
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.
180
+
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.
185
+
186
+ ### Rake files for automation
187
+
188
+ We provide rake tasks for starting up the development server and building the React Router application.
189
+
190
+ Note that the build task is attached to the `assets:precompile` task.
191
+ This means
192
+ that you do not need
193
+ to add extra configuration to your CI/CD scripts
194
+ to build the React Router app since it should normally call this task already.
195
+
196
+ If your CI/CD already installs Node (which is required for building), then you probably won't have to touch your CI/CD scripts at all.
197
+
198
+ ### Additional React Router and Vite Configuration
199
+
200
+ We currently serve the React application from the `/react/*` paths.
201
+ All other paths are handled by Rails.
202
+ The current gem adds minor configurations for this.
203
+ If you want a different setup, you can change the configurations.
204
+
205
+ ## Demo and Source code
206
+
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.
209
+ * The source-code for this demo application is [available on GitHub](https://github.com/naofumi/react-router-vite-rails).
210
+
211
+ The source code is heavily commented. We recommend that you read through it to understand the setup in more detail.
212
+
213
+ ## Using the gem
214
+
215
+ ### Install Ruby on Rails
216
+
217
+ This gem works with a pre-existing installation of Rails. Create a new Rails application if you haven't already.
218
+
219
+ ```shell
220
+ rails new [project name]
221
+ ```
222
+
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
+ ```
233
+
234
+ ### Install the `react_router_rails_spa` gem
235
+
236
+ Add the following line to your `Gemfile`.
237
+
238
+ ```ruby
239
+ gem "react_router_rails_spa"
240
+ ```
241
+
242
+ Install the gem
243
+
244
+ ```shell
245
+ bundle install
246
+ ```
247
+
248
+ We recommend committing your changes at this point before the following generator adds and modifies your files.
249
+
250
+ Run the generator
251
+
252
+ ```shell
253
+ bin/rails generate react_router_rails_spa:install
254
+ ```
255
+
256
+ This will install the latest version of React Router
257
+ and generate the routes and all the necessary files and configurations.
258
+
259
+ ### Run the development server
260
+
261
+ Run the following command to start the frontend development server with HMR (Hot Module Replacement).
262
+
263
+ ```shell
264
+ bin/rails react_router:dev
265
+ ```
266
+
267
+ Point your browser to http://localhost:5173/react/ to see the welcome page.
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
274
+
275
+ Run the following command to build the React Router application
276
+ and store the static files into the Rails `public` directory.
277
+
278
+ ```shell
279
+ bin/rails react_router:build
280
+ ```
281
+
282
+ This command is also aliased as:
283
+
284
+ ```shell
285
+ bin/rails react_router:preview
286
+ ```
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
291
+
292
+ We have added numerous comments to the code generated by this gem. Please read it to understand how the integration works.
293
+
@@ -0,0 +1,20 @@
1
+ # This concern sets the CSRF token inside the "X-CSRF-Token" cookie,
2
+ # allowing you to easily use the robust CSRF protection that Ruby on Rails provides
3
+ # inside your React Router app.
4
+ #
5
+ # Refer to `frontend/app/utilities/csrf.ts` to see
6
+ # how the client side is implemented.
7
+ module ReactRouterRailsSpa
8
+ module CsrfCookieEnabled
9
+ extend ActiveSupport::Concern
10
+ included do
11
+ before_action :set_csrf_cookie
12
+ end
13
+
14
+ private
15
+
16
+ def set_csrf_cookie
17
+ cookies["X-CSRF-Token"] = form_authenticity_token
18
+ end
19
+ end
20
+ end
@@ -6,19 +6,54 @@ module ReactRouterRailsSpa
6
6
  source_root File.expand_path("templates", __dir__)
7
7
 
8
8
  def create_react_router_app
9
- say "Setting up React Router v7 SBA mode in Rails..."
9
+ say "Downloading React Router v7 ..."
10
10
  inside Rails.root do
11
11
  run "npx create-react-router@latest frontend --yes --no-git-init"
12
12
  end
13
13
  end
14
14
 
15
15
  def setup_rails_routes
16
- route 'get "react/*path", to: "react#show"'
16
+ say "Setting up Rails routes ..."
17
+ route <<~RUBY
18
+ # When we use React Router inside a subdirectory, it works better if we
19
+ # use a trailing slash for the root path.
20
+ # This redirects from "/react-router" to "/react-router/".
21
+ get "react", to: redirect("/react/"), constraints: ->(req) {
22
+ req.original_url.last != "/"
23
+ }
24
+
25
+ # All requests to `/react/*` are handled by ReactController#show.
26
+ match "react", to: "react#show", via: :all
27
+ get "react/*path", to: "react#show"
28
+ RUBY
17
29
  end
18
30
 
19
31
  def create_react_controller
20
- create_file "app/controllers/react_controller.rb", <<-RUBY
32
+ say "Creating the React bootstrap endpoint controller ..."
33
+ create_file "app/controllers/react_controller.rb", <<~RUBY
34
+ # This controller provides the catch-all action for React Router.
35
+ # This will render the bootstrap HTML file
36
+ # for all React Router requests.
37
+ #
38
+ # Unlike typical webpack or esbuild setups, we do not generate the bootstrap HTML file from ERB templates which include
39
+ # `javascript_include_tag` (propshaft, sprockets) or `javascript_pack_tag` (webpack).
40
+ # Instead, we take the index.html file that was generated by the React Router build
41
+ # and rename it to "react-router-rails-spa-index.html".
42
+ # We then serve this from the controller action in response to the bootstrap HTML file request.
43
+ #
44
+ # Benefits:
45
+ #
46
+ # * We can use the index.html file that React Router generates using SSG,
47
+ # from the `frontend/app/root.tsx` file.
48
+ # This file contains optimizations that would be challenging to recreate inside Rails using ERB templates.
49
+ # * By going through the Rails controller, we can adjust the cache and cookie headers to
50
+ # improve performance, reliability, and integration with Rails.
51
+ #
52
+ # The included ReactRouterRailsSpa::CsrfCookieEnabled module will
53
+ # send the CSRF token inside the "X-CSRF-Token" cookie for use inside your React app.
21
54
  class ReactController < ApplicationController
55
+ include ReactRouterRailsSpa::CsrfCookieEnabled
56
+
22
57
  def show
23
58
  render file: Rails.public_path.join("react/react-router-rails-spa-index.html"), layout: false
24
59
  end
@@ -26,20 +61,19 @@ module ReactRouterRailsSpa
26
61
  RUBY
27
62
  end
28
63
 
29
- def create_public_folders
30
- empty_directory Rails.root.join("public/react")
31
- end
32
-
33
64
  def copy_rake_task
65
+ say "Copying Rake tasks ..."
34
66
  template "react.rake", "lib/tasks/react.rake"
35
67
  end
36
68
 
37
69
  def copy_react_router_configs
70
+ say "Copying React Router configurations ..."
38
71
  template "react-router.config.ts", "frontend/react-router.config.ts"
39
72
  template "vite.config.ts", "frontend/vite.config.ts"
40
73
  end
41
74
 
42
75
  def copy_react_app_files
76
+ say "Copying React Router application pages and utilities ..."
43
77
  template "home.tsx", "frontend/app/routes/home.tsx"
44
78
  directory "welcome", "frontend/app/welcome"
45
79
  template "csrf.ts", "frontend/app/utilities/csrf.ts"
@@ -14,13 +14,13 @@
14
14
  *
15
15
  * How it works.
16
16
  *
17
- * 1. The Ruby on Rails server sends the session-specific
17
+ * 1. The Ruby on Rails server creates and sends the session-specific
18
18
  * CSRF token in a Cookie named "X-CSRF-Token"
19
19
  * app/controllers/concerns/csrf_cookie_enabled.rb
20
20
  * 2. Retrieve this token with the `getCSRFToken()` function
21
21
  * and use its value to send in your fetch request headers.
22
22
  * 3. On receiving the request, Rails will validate that the
23
- * 'X-CSRF-Token' header value matches what was sent via the cookie.
23
+ * 'X-CSRF-Token' header value is identical to the session-specific token.
24
24
  * 4. Note that you only need to do this for non-GET requests assuming that
25
25
  * you are following best practices and not mutating data in GET requests.
26
26
  *
@@ -36,6 +36,10 @@
36
36
  * }
37
37
  * )
38
38
  *
39
+ * Note that Axios has a feature that automatically does this for you.
40
+ * Read the XSRF configuration comments on the linked page.
41
+ * https://axios-http.com/docs/req_config
42
+ *
39
43
  * */
40
44
 
41
45
  export function getCSRFToken() {
@@ -0,0 +1,18 @@
1
+ # This concern sets the CSRF token inside the "X-CSRF-Token" cookie,
2
+ # allowing you to easily use the robust CSRF protection that Ruby on Rails provides
3
+ # inside your React Router app.
4
+ #
5
+ # Refer to `frontend/app/utilities/csrf.ts` to see
6
+ # how the client side is implemented.
7
+ module CsrfCookieEnabled
8
+ extend ActiveSupport::Concern
9
+ included do
10
+ before_action :set_csrf_cookie
11
+ end
12
+
13
+ private
14
+
15
+ def set_csrf_cookie
16
+ cookies["X-CSRF-Token"] = form_authenticity_token
17
+ end
18
+ end
@@ -1,17 +1,24 @@
1
1
  /*
2
- * In this example application, the Ruby on Rails APIs endpoints are like
3
- * `GET /posts` or `GET /users` and do not have any prefixes like `/api*`.
2
+ * The `baseApiPath()` is useful when you are using the React Router development server
3
+ * and the JSON API endpoints on your Rails server do not have specific prefixes.
4
+ * It is not used in production or preview builds.
4
5
  *
5
- * However, during development, the port for the Vite development server (typically 5173) is different from
6
+ *
7
+ * In the example application, the Ruby on Rails API endpoints are like
8
+ * `GET /posts` or `GET /users` and do not have any specific prefixes like `/api*`.
9
+ *
10
+ * However, during development, the port for the React Router development server (typically 5173) is different from
6
11
  * the Rails development server (typically 3000).
7
- * This means that the Vite server needs to distinguish
12
+ * This means that the React Router server needs to distinguish
8
13
  * between the requests it should send to the Rails server on port 3000 (JSON API requests) and the
9
- * ones that it should handle itself on port 5173 (e.g., the Vite assets).
14
+ * ones that it should handle itself on port 5173 (e.g., the React Router assets).
10
15
  *
11
- * Therefore, when running on the Vite development server, we prefix
12
- * requests intended for the Rails API server with "/api" to tell Vite to send them
16
+ * Therefore, when running on the React Router development server, we prefix
17
+ * requests intended for the Rails API server with "/api" to tell the development server to send them
13
18
  * to port 3000.
14
- * See the `proxy:` section in `frontend-react-router/vite.config.ts` for
19
+ * The all other requests will be sent to port 5173.
20
+ *
21
+ * See the `proxy:` section in `frontend/vite.config.ts` for
15
22
  * the Vite side of this configuration.
16
23
  *
17
24
  * Using this function, you would write a request to the Rails API as follows.
@@ -20,8 +27,8 @@
20
27
  * headers: { "Accept": "application/json" }
21
28
  * }).then(...)....
22
29
  *
23
- * Note that if your API server's endpoints are like `/api/posts`,
24
- * then you can write like the following, without the `baseApiPath()` function.
30
+ * Note that if your JSON API server's endpoints are like `/api/posts`,
31
+ * then you don't need the `baseApiPath()` function.
25
32
  *
26
33
  * fetch(`/api/posts`, {
27
34
  * headers: { "Accept": "application/json" }
@@ -2,11 +2,10 @@ import type { Config } from "@react-router/dev/config";
2
2
 
3
3
  export default {
4
4
  // Config options...
5
- // For our React Router app, we will turn off Server-side render
6
- // use SPA mode!!
5
+ // For our React Router app, we will turn off Server-side render and use SPA mode.
7
6
  ssr: false,
8
- // In the above, we have decided to serve the React Router app from "/react-router/".
9
- // The basename options tell React Router to manage this when generating
7
+ // We serve the React Router app from the "/react/" path.
8
+ // The basename option tells React Router to manage this when generating
10
9
  // Link tags, for example.
11
- basename: "/react-router/"
10
+ basename: "/react/"
12
11
  } satisfies Config;
@@ -1,17 +1,4 @@
1
- namespace :react do
2
- desc "Build the React application"
3
- task :build do
4
- puts "Building React Router v7 app..."
5
- system("cd frontend && npm install && npm run build")
6
-
7
- puts "Moving build files to public/react-router..."
8
- system("rm -rf public/react-router/*")
9
- system("mv frontend/dist/* public/react-router/")
10
- system("mv public/react-router/index.html public/react-router/react-router-index.html")
11
-
12
- puts "✅ React app successfully built and deployed!"
13
- end
14
- end
1
+ # frozen_string_literal: true
15
2
 
16
3
  namespace :react_router do
17
4
  # For convenience, npm packages do not have to be explicitly installed.
@@ -30,9 +17,9 @@ namespace :react_router do
30
17
  # this task in the Procfile.
31
18
  #
32
19
  # bin/rails react_router:dev
33
- desc "Start React Router Dev Server"
20
+ desc "Start React Router Development Server with Hot Module Reloading"
34
21
  task dev: [ :npm_install ] do
35
- puts "Starting React Router v7 app dev server..."
22
+ puts "Starting React Router v7 app development server..."
36
23
  Dir.chdir("#{Dir.pwd}/frontend") do
37
24
  system("npm", "run", "dev")
38
25
  end
@@ -54,21 +41,26 @@ namespace :react_router do
54
41
  # Running bin/rails assets:precompile will also run this task.
55
42
  #
56
43
  # bin/rails react_router:build
57
- desc "Build React Router App"
44
+ desc "Build React Router App and move to the public folder"
58
45
  task build: [ :npm_install ] do
59
- Dir.chdir("#{Dir.pwd}/frontend") do
60
- puts "Building React Router v7 app..."
61
- system("npm", "run", "build")
46
+ puts "Building React Router v7 app..."
47
+ `cd frontend && npm run build`
62
48
 
63
- puts "Moving build files to public/react..."
64
- system("rm -rf public/react/*")
65
- system("mv frontend/build/client public/react")
66
- system("mv public/react/index.html public/react/react-router-rails-spa-index.html")
49
+ puts "Moving build files to public/react..."
50
+ `rm -rf public/react`
51
+ `mv frontend/build/client public/react`
52
+ `mv public/react/index.html public/react/react-router-rails-spa-index.html`
67
53
 
68
- puts "✅ React app successfully built and deployed!"
69
- end
54
+ puts "✅ React app successfully built and deployed!"
70
55
  end
71
56
 
57
+ # Run bin/rails react_router:preview to create a preview build.
58
+ #
59
+ # This is identical to running bin/rails react_router: build
60
+ # and is provided solely to align better with intent.
61
+ desc "Preview your React Router App from the Rails development server (typically port 3000)"
62
+ task preview: [ :build ]
63
+
72
64
  # Run bin/rails react_router:clobber to remove the build files.
73
65
  # Running bin/rails assets:clobber will also run this task.
74
66
  task :clobber do
@@ -3,32 +3,32 @@ import tailwindcss from "@tailwindcss/vite";
3
3
  import { defineConfig } from "vite";
4
4
  import tsconfigPaths from "vite-tsconfig-paths";
5
5
 
6
+ const railsEnv = process.env.RAILS_ENV || 'development'
7
+
6
8
  export default defineConfig({
7
9
  plugins: [tailwindcss(), reactRouter(), tsconfigPaths()],
8
10
  build: {
9
11
  assetsDir: "assets",
10
- // Set to true if you want sourcemaps
11
- sourcemap: true,
12
+ // Set to true if you want sourcemaps in your build.
13
+ sourcemap: (["test", "development"].indexOf(railsEnv) !== -1) ? true : false,
12
14
  },
13
- // Like in react-router.config.ts, this tells Vite that
14
- // the app is served from the "/react-router/" sub-path.
15
- // Change this if you want to use a different sub-path name.
16
- base: "/react-router/",
15
+ // As also defined in react-router.config.ts, this tells Vite that
16
+ // the app is served from the "/react/" sub-path.
17
+ base: "/react/",
17
18
 
18
19
  // These are settings for the dev server.
19
20
  server: {
20
- // In production, the API server (Rails) and the assets
21
- // will be served from the same port number (typically 80).
21
+ // In production and preview mode, the API server (Rails) and the assets
22
+ // will be served from the same port number (typically 80 for production and 3000 for preview).
22
23
  //
23
- // However, in development, assets will be served from the
24
- // Vite dev server on port 5173, and the API server will be on
25
- // port 3000 (Rails default).
26
- // The use of different ports can complicate CORS setting and cookie handling.
24
+ // However, when using the vite development server, assets will be served from port 5173,
25
+ // and the API server will be on port 3000 (Rails default).
26
+ // The use of different ports complicates CORS settings and cookie handling.
27
27
  //
28
- // To prevent this problem, Vite provides a proxy mode that can receive API requests
29
- // on port (5173) and forward this to the Rails server (port 3000).
30
- // The browser will think that all communication happens on port 5173 so CORS
31
- // is no longer necessary.
28
+ // To work around this, Vite provides a proxy mode that can receive API requests
29
+ // on port (5173) and forward them to the Rails server (port 3000).
30
+ // From the browser's viewpoint, all communications including API requests to the Rails server
31
+ // will now happen on port 5173, so CORS will no longer be necessary.
32
32
  proxy: {
33
33
  // Any requests starting with "/api" will be forwarded according to
34
34
  // the following rules.
@@ -44,6 +44,8 @@ export default defineConfig({
44
44
  // Note that if the Rails server has a dedicated namespace for APIs,
45
45
  // then you can just use that instead of "/api", and the following
46
46
  // rewrite rule will not be necessary.
47
+ //
48
+ // Also see `frontend/app/utilities/proxy.ts` for how we handle this inside React.
47
49
  rewrite: (path) => path.replace(/^\/api/, ""),
48
50
  },
49
51
  },
@@ -5,7 +5,7 @@ import railsLogo from "./rails-logo.svg";
5
5
  export function Welcome() {
6
6
  return (
7
7
  <main className="flex items-center justify-center pt-16 pb-4">
8
- <div className="flex-1 flex flex-col items-center gap-16 min-h-0">
8
+ <div className="flex-1 flex flex-col items-center min-h-0">
9
9
  <header className="flex flex-row items-baseline gap-9 max-w-[100vw]">
10
10
  <div className="w-[300px]">
11
11
  <img
@@ -28,16 +28,18 @@ export function Welcome() {
28
28
  />
29
29
  </div>
30
30
  </header>
31
- <div className="max-w-[300px] w-full space-y-6 px-4">
31
+ <div className="mt-12 text-2xl text-gray-600">with the</div>
32
+ <h1 className="mt-6 text-3xl font-bold">react_router_rails_spa gem</h1>
33
+ <div className="mt-12 max-w-[300px] w-full space-y-6 px-4">
32
34
  <nav className="space-y-4">
33
- <p className="leading-6 text-gray-700 dark:text-gray-200 text-center">
35
+ <p className="text-2xl leading-6 text-gray-700 dark:text-gray-200 text-center">
34
36
  What&apos;s next?
35
37
  </p>
36
38
  <div className="text-center">
37
39
  {resources.map(({ href, text }) => (
38
40
  <a
39
41
  key={href}
40
- className="block mb-2 leading-normal text-blue-700 hover:underline dark:text-blue-500"
42
+ className="block mb-4 leading-tight text-blue-700 hover:underline dark:text-blue-500"
41
43
  href={href}
42
44
  target="_blank"
43
45
  rel="noreferrer"
@@ -54,12 +56,20 @@ export function Welcome() {
54
56
  }
55
57
 
56
58
  const resources = [
59
+ {
60
+ href: "https://github.com/naofumi/react_router_rails_spa",
61
+ text: "Read the README for the react_router_rails_spa gem",
62
+ },
63
+ {
64
+ href: "https://github.com/naofumi/react_router_rails_spa/blob/main/documents/introduction.md",
65
+ text: "Read the Introduction for the react_router_rails_spa gem",
66
+ },
57
67
  {
58
68
  href: "https://reactrouter.com/docs",
59
- text: "React Router Docs",
69
+ text: "Read the React Router Docs",
60
70
  },
61
71
  {
62
- href: "https://rmx.as/discord",
63
- text: "Join Discord",
72
+ href: "https://rubyonrails.org/",
73
+ text: "Read the Ruby on Rails Docs",
64
74
  },
65
75
  ];
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ReactRouterRailsSpa
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.2"
5
5
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "react_router_rails_spa/version"
4
4
  require_relative "react_router_rails_spa/generators/install_generator"
5
+ require_relative "react_router_rails_spa/csrf/csrf_cookie_enabled"
5
6
 
6
7
  module ReactRouterRailsSpa
7
8
  end
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.0
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-07 00:00:00.000000000 Z
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
@@ -32,7 +18,6 @@ extensions: []
32
18
  extra_rdoc_files: []
33
19
  files:
34
20
  - ".idea/.gitignore"
35
- - ".idea/.name"
36
21
  - ".idea/inspectionProfiles/Project_Default.xml"
37
22
  - ".idea/misc.xml"
38
23
  - ".idea/modules.xml"
@@ -44,9 +29,12 @@ files:
44
29
  - LICENSE.txt
45
30
  - README.md
46
31
  - Rakefile
32
+ - documents/introduction.md
47
33
  - lib/react_router_rails_spa.rb
34
+ - lib/react_router_rails_spa/csrf/csrf_cookie_enabled.rb
48
35
  - lib/react_router_rails_spa/generators/install_generator.rb
49
36
  - lib/react_router_rails_spa/generators/templates/csrf.ts
37
+ - lib/react_router_rails_spa/generators/templates/csrf_cookie_enabled.rb
50
38
  - lib/react_router_rails_spa/generators/templates/home.tsx
51
39
  - lib/react_router_rails_spa/generators/templates/proxy.ts
52
40
  - lib/react_router_rails_spa/generators/templates/react-router.config.ts
@@ -65,6 +53,7 @@ metadata:
65
53
  homepage_uri: https://github.com/naofumi/react_router_rails_spa
66
54
  source_code_uri: https://github.com/naofumi/react_router_rails_spa
67
55
  changelog_uri: https://github.com/naofumi/react_router_rails_spa
56
+ rubygems_mfa_required: 'true'
68
57
  post_install_message:
69
58
  rdoc_options: []
70
59
  require_paths:
@@ -83,5 +72,5 @@ requirements: []
83
72
  rubygems_version: 3.4.10
84
73
  signing_key:
85
74
  specification_version: 4
86
- summary: Use React Router v7 framework on your Rails-powered SPA.
75
+ summary: Integrate React Router v7 SPA framework with your Ruby on Rails backend.
87
76
  test_files: []
data/.idea/.name DELETED
@@ -1 +0,0 @@
1
- re_rou_ra