Skip to content

Commit bfa1c58

Browse files
authored
Improve bundle path resolution with secure server bundle locations (#1798)
This PR enhances React on Rails server bundle path resolution with smart fallback logic while maintaining security through the new enforce_private_server_bundles configuration option. Key improvements: - Smart fallback: Try private paths first, then public when not enforced - Security option: enforce_private_server_bundles prevents public fallback - Better UX: Convert generated_assets_dirs deprecation from error to warning - Clear messaging: Distinguish between public and private asset paths - Comprehensive tests: Updated all test expectations for new behavior New installations default to enforce_private_server_bundles=true for security. Existing apps continue working with improved bundle resolution reliability.
1 parent f77bd54 commit bfa1c58

File tree

17 files changed

+471
-106
lines changed

17 files changed

+471
-106
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,5 +61,8 @@ yalc.lock
6161
# Generated by ROR FS-based Registry
6262
generated
6363

64+
# Server-side rendering generated bundles
65+
ssr-generated
66+
6467
# Claude Code local settings
6568
.claude/settings.local.json

CHANGELOG.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,45 @@ After a release, please make sure to run `bundle exec rake update_changelog`. Th
2323

2424
Changes since the last non-beta release.
2525

26+
#### Breaking Changes
27+
28+
- **Removed `generated_assets_dirs` configuration**: The legacy `config.generated_assets_dirs` option is no longer supported and will raise an error if used. Since Shakapacker is now required, asset paths are automatically determined from `shakapacker.yml` configuration. Remove any `config.generated_assets_dirs` from your `config/initializers/react_on_rails.rb` file. Use `public_output_path` in `config/shakapacker.yml` to customize asset output location instead. [PR 1798](https://github.com/shakacode/react_on_rails/pull/1798)
29+
30+
#### New Features
31+
32+
- **Server Bundle Security**: Added new configuration options for enhanced server bundle security and organization:
33+
34+
- `server_bundle_output_path`: Configurable directory (relative to the Rails root) for server bundle output (default: "ssr-generated"). If set to `nil`, the server bundle will be loaded from the same public directory as client bundles.
35+
- `enforce_private_server_bundles`: When enabled, ensures server bundles are only loaded from private directories outside the public folder (default: false for backward compatibility)
36+
37+
- **Improved Bundle Path Resolution**: Bundle path resolution for server bundles now works as follows:
38+
- If `server_bundle_output_path` is set, the server bundle is loaded from that directory.
39+
- If `server_bundle_output_path` is not set, the server bundle falls back to the client bundle directory (typically the public output path).
40+
- If `enforce_private_server_bundles` is enabled:
41+
- The server bundle will only be loaded from the private directory specified by `server_bundle_output_path`.
42+
- If the bundle is not found there, it will _not_ fall back to the public directory.
43+
- If `enforce_private_server_bundles` is not enabled and the bundle is not found in the private directory, it will fall back to the public directory.
44+
- This logic ensures that, when strict enforcement is enabled, server bundles are never loaded from public directories, improving security and clarity of bundle resolution.
45+
46+
#### API Improvements
47+
48+
- **Method Naming Clarification**: Added `public_bundles_full_path` method to clarify bundle path handling:
49+
- `public_bundles_full_path`: New method specifically for webpack bundles in public directories
50+
- `generated_assets_full_path`: Now deprecated (backwards-compatible alias)
51+
- This eliminates confusion between webpack bundles and general Rails public assets
52+
53+
#### Security Enhancements
54+
55+
- **Private Server Bundle Enforcement**: When `enforce_private_server_bundles` is enabled, server bundles bypass public directory fallbacks and are only loaded from designated private locations
56+
- **Path Validation**: Added validation to ensure `server_bundle_output_path` points to private directories when enforcement is enabled
57+
58+
#### Bug Fixes
59+
60+
- **Non-Packer Environment Compatibility**: Fixed potential NoMethodError when using bundle path resolution in environments without Shakapacker
61+
- **Shakapacker version requirements**: Fixed inconsistent version requirements between basic pack generation (6.5.1+) and advanced auto-bundling features (7.0.0+). Added backward compatibility for users on Shakapacker 6.5.1-6.9.x while providing clear upgrade guidance for advanced features. Added new constants `MINIMUM_SHAKAPACKER_VERSION_FOR_AUTO_BUNDLING` and improved version checking performance with caching. [PR 1798](https://github.com/shakacode/react_on_rails/pull/1798)
62+
63+
### [16.0.1-rc.2] - 2025-09-20
64+
2665
#### Bug Fixes
2766

2867
- **Packs generator**: Fixed error when `server_bundle_js_file` configuration is empty (default). Added safety check to prevent attempting operations on invalid file paths when server-side rendering is not configured. [PR 1802](https://github.com/shakacode/react_on_rails/pull/1802)

docs/guides/configuration.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,51 @@ ReactOnRails.configure do |config|
129129
# This manifest file is automatically generated by the React Server Components Webpack plugin. Only set this if you've configured the plugin to use a different filename.
130130
config.react_server_client_manifest_file = "react-server-client-manifest.json"
131131

132+
################################################################################
133+
# SERVER BUNDLE SECURITY AND ORGANIZATION
134+
################################################################################
135+
136+
# This configures the directory (relative to the Rails root) where the server bundle will be output.
137+
# By default, this is "ssr-generated". If set to nil, the server bundle will be loaded from the same
138+
# public directory as client bundles. For enhanced security, use this option in conjunction with
139+
# `enforce_private_server_bundles` to ensure server bundles are only loaded from private directories
140+
# config.server_bundle_output_path = "ssr-generated"
141+
142+
# When set to true, React on Rails will only load server bundles from private, explicitly configured directories (such as `ssr-generated`), and will raise an error if a server bundle is found in a public or untrusted location. This helps prevent accidental or malicious execution of untrusted JavaScript on the server, and is strongly recommended for production environments. And prevent leakage of server-side code to the client (Especially in the case of RSC).
143+
# Default is false for backward compatibility, but enabling this option is a best practice for security.
144+
config.enforce_private_server_bundles = false
145+
146+
################################################################################
147+
# BUNDLE ORGANIZATION EXAMPLES
148+
################################################################################
149+
#
150+
# This configuration creates a clear separation between client and server assets:
151+
#
152+
# CLIENT BUNDLES (Public, Web-Accessible):
153+
# Location: public/webpack/[environment]/ or public/packs/ (According to your shakapacker.yml configuration)
154+
# Files: application.js, manifest.json, CSS files
155+
# Served by: Web server directly
156+
# Access: ReactOnRails::Utils.public_bundles_full_path
157+
#
158+
# SERVER BUNDLES (Private, Server-Only):
159+
# Location: ssr-generated/ (when server_bundle_output_path configured)
160+
# Files: server-bundle.js, rsc-bundle.js
161+
# Served by: Never served to browsers
162+
# Access: ReactOnRails::Utils.server_bundle_js_file_path
163+
#
164+
# Example directory structure with recommended configuration:
165+
# app/
166+
# ├── ssr-generated/ # Private server bundles
167+
# │ ├── server-bundle.js
168+
# │ └── rsc-bundle.js
169+
# └── public/
170+
# └── webpack/development/ # Public client bundles
171+
# ├── application.js
172+
# ├── manifest.json
173+
# └── styles.css
174+
#
175+
################################################################################
176+
132177
# `prerender` means server-side rendering
133178
# default is false. This is an option for view helpers `render_component` and `render_component_hash`.
134179
# Set to true to change the default value to true.

lib/generators/react_on_rails/base_generator.rb

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def add_hello_world_route
2424
end
2525

2626
def create_react_directories
27-
# Create auto-registration directory structure for non-Redux components only
27+
# Create auto-bundling directory structure for non-Redux components only
2828
# Redux components handle their own directory structure
2929
return if options.redux?
3030

@@ -136,14 +136,17 @@ def update_gitignore_for_auto_registration
136136
return unless File.exist?(gitignore_path)
137137

138138
gitignore_content = File.read(gitignore_path)
139-
return if gitignore_content.include?("**/generated/**")
140139

141-
append_to_file ".gitignore" do
142-
<<~GITIGNORE
140+
additions = []
141+
additions << "**/generated/**" unless gitignore_content.include?("**/generated/**")
142+
additions << "ssr-generated" unless gitignore_content.include?("ssr-generated")
143+
144+
return if additions.empty?
143145

144-
# Generated React on Rails packs
145-
**/generated/**
146-
GITIGNORE
146+
append_to_file ".gitignore" do
147+
lines = ["\n# Generated React on Rails packs"]
148+
lines.concat(additions)
149+
"#{lines.join("\n")}\n"
147150
end
148151
end
149152

lib/generators/react_on_rails/react_with_redux_generator.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class ReactWithReduxGenerator < Rails::Generators::Base
1919
aliases: "-T"
2020

2121
def create_redux_directories
22-
# Create auto-registration directory structure for Redux
22+
# Create auto-bundling directory structure for Redux
2323
empty_directory("app/javascript/src/HelloWorldApp/ror_components")
2424

2525
# Create Redux support directories within the component directory
@@ -31,7 +31,7 @@ def copy_base_files
3131
base_js_path = "redux/base"
3232
ext = component_extension(options)
3333

34-
# Copy Redux-connected component to auto-registration structure
34+
# Copy Redux-connected component to auto-bundling structure
3535
copy_file("#{base_js_path}/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.client.#{ext}",
3636
"app/javascript/src/HelloWorldApp/ror_components/HelloWorldApp.client.#{ext}")
3737
copy_file("#{base_js_path}/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.server.#{ext}",

lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@ ReactOnRails.configure do |config|
4343
#
4444
config.server_bundle_js_file = "server-bundle.js"
4545

46+
# Configure where server bundles are output. Defaults to "ssr-generated".
47+
# This should match your webpack configuration for server bundles.
48+
config.server_bundle_output_path = "ssr-generated"
49+
50+
# Enforce that server bundles are only loaded from private (non-public) directories.
51+
# When true, server bundles will only be loaded from the configured server_bundle_output_path.
52+
# This is recommended for production to prevent server-side code from being exposed.
53+
config.enforce_private_server_bundles = true
54+
4655
################################################################################
4756
################################################################################
4857
# FILE SYSTEM BASED COMPONENT REGISTRY

lib/generators/react_on_rails/templates/base/base/config/webpack/serverWebpackConfig.js.tt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,14 @@ const configureServer = () => {
4444

4545
// Custom output for the server-bundle that matches the config in
4646
// config/initializers/react_on_rails.rb
47+
// Server bundles are output to a private directory (not public) for security
4748
serverWebpackConfig.output = {
4849
filename: 'server-bundle.js',
4950
globalObject: 'this',
5051
// If using the React on Rails Pro node server renderer, uncomment the next line
5152
// libraryTarget: 'commonjs2',
52-
path: config.outputPath,
53-
publicPath: config.publicPath,
53+
path: require('path').resolve(__dirname, '../../ssr-generated'),
54+
// No publicPath needed since server bundles are not served via web
5455
// https://webpack.js.org/configuration/output/#outputglobalobject
5556
};
5657

0 commit comments

Comments
 (0)