Skip to content
Closed
228 changes: 228 additions & 0 deletions docs/api-reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,234 @@ ReactOnRails.configure do |config|
# This controls what command is run to build assets during tests
################################################################################
config.build_test_command = "RAILS_ENV=test bin/shakapacker"

#
# React Server Components and Streaming SSR are React on Rails Pro features.
# For detailed configuration of RSC and streaming features, see:
# https://github.com/shakacode/react_on_rails/blob/master/react_on_rails_pro/docs/configuration.md
#
# Key Pro configurations (configured in ReactOnRailsPro.configure block):
# - rsc_bundle_js_file: Path to RSC bundle
# - react_client_manifest_file: Client component manifest for RSC
# - react_server_client_manifest_file: Server manifest for RSC
# - enable_rsc_support: Enable React Server Components
#
# See Pro documentation for complete setup instructions.

################################################################################
# SERVER BUNDLE SECURITY AND ORGANIZATION
################################################################################

# ⚠️ RECOMMENDED: Use Shakapacker 9.0+ for Automatic Configuration
#
# For Shakapacker 9.0+, add to config/shakapacker.yml:
# private_output_path: ssr-generated
#
# React on Rails will automatically detect and use this value, eliminating the need
# to configure server_bundle_output_path here. This provides a single source of truth.
#
# For older Shakapacker versions or custom setups, manually configure:
# This configures the directory (relative to the Rails root) where the server bundle will be output.
# By default, this is "ssr-generated". If set to nil, the server bundle will be loaded from the same
# public directory as client bundles. For enhanced security, use this option in conjunction with
# `enforce_private_server_bundles` to ensure server bundles are only loaded from private directories
# config.server_bundle_output_path = "ssr-generated"

# 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).
# Default is false for backward compatibility, but enabling this option is a best practice for security.
config.enforce_private_server_bundles = false

################################################################################
# BUNDLE ORGANIZATION EXAMPLES
################################################################################
#
# This configuration creates a clear separation between client and server assets:
#
# CLIENT BUNDLES (Public, Web-Accessible):
# Location: public/webpack/[environment]/ or public/packs/ (According to your shakapacker.yml configuration)
# Files: application.js, manifest.json, CSS files
# Served by: Web server directly
# Access: ReactOnRails::Utils.public_bundles_full_path
#
# SERVER BUNDLES (Private, Server-Only):
# Location: ssr-generated/ (when server_bundle_output_path configured)
# Files: server-bundle.js, rsc-bundle.js
# Served by: Never served to browsers
# Access: ReactOnRails::Utils.server_bundle_js_file_path
#
# Example directory structure with recommended configuration:
# app/
# ├── ssr-generated/ # Private server bundles
# │ ├── server-bundle.js
# │ └── rsc-bundle.js
# └── public/
# └── webpack/development/ # Public client bundles
# ├── application.js
# ├── manifest.json
# └── styles.css
#
################################################################################

# `prerender` means server-side rendering
# default is false. This is an option for view helpers `render_component` and `render_component_hash`.
# Set to true to change the default value to true.
config.prerender = false

# THE BELOW OPTIONS FOR SERVER-SIDE RENDERING RARELY NEED CHANGING
#
# This value only affects server-side rendering when using the webpack-dev-server
# If you are hashing the server bundle and you want to use the same bundle for client and server,
# you'd set this to `true` so that React on Rails reads the server bundle from the webpack-dev-server.
# Normally, you have different bundles for client and server, thus, the default is false.
# Furthermore, if you are not hashing the server bundle (not in the manifest.json), then React on Rails
# will only look for the server bundle to be created in the typical file location, typically by
# a `shakapacker --watch` process.
# If true, ensure that in config/shakapacker.yml that you have both dev_server.hmr and
# dev_server.inline set to false.
config.same_bundle_for_client_and_server = false

# If set to true, this forces Rails to reload the server bundle if it is modified
# Default value is Rails.env.development?
# You probably will never change this.
config.development_mode = Rails.env.development?

# For server rendering so that the server-side console replays in the browser console.
# This can be set to false so that server side messages are not displayed in the browser.
# Default is true. Be cautious about turning this off, as it can make debugging difficult.
# Default value is true
config.replay_console = true

# Default is true. Logs server rendering messages to Rails.logger.info. If false, you'll only
# see the server rendering messages in the browser console.
config.logging_on_server = true

# Default is true only for development? to raise exception on server if the JS code throws for
# server rendering. The reason is that the server logs will show the error and force you to fix
# any server rendering issues immediately during development.
config.raise_on_prerender_error = Rails.env.development?

# This configuration allows logic to be applied to client rendered props, such as stripping props that are only used during server rendering.
# Add a module with an adjust_props_for_client_side_hydration method that expects the component's name & props hash
# See below for an example definition of RenderingPropsExtension
config.rendering_props_extension = RenderingPropsExtension

################################################################################
# Server Renderer Configuration for ExecJS
################################################################################
# The default server rendering is ExecJS, by default using Node.js runtime
# If you wish to use an alternative Node server rendering for higher performance,
# contact justin@shakacode.com for details.
#
# For ExecJS:
# You can configure your pool of JS virtual machines and specify where it should load code:
# On MRI, use `node.js` runtime for the best performance
# (see https://github.com/shakacode/react_on_rails/issues/1438)
# Also see https://github.com/shakacode/react_on_rails/issues/1457#issuecomment-1165026717 if using `mini_racer`
# On MRI, you'll get a deadlock with `pool_size` > 1
# If you're using JRuby, you can increase `pool_size` to have real multi-threaded rendering.
config.server_renderer_pool_size = 1 # increase if you're on JRuby
config.server_renderer_timeout = 20 # seconds

################################################################################
################################################################################
# FILE SYSTEM BASED COMPONENT REGISTRY
# `render_component` and `render_component_hash` view helper methods can
# auto-load the bundle for the generated component, to avoid having to specify the
# bundle manually for each view with the component.
#
# SHAKAPACKER VERSION REQUIREMENTS:
# - Basic pack generation: Shakapacker 6.5.1+
# - Advanced auto-registration with nested entries: Shakapacker 7.0.0+
# - Async loading support: Shakapacker 8.2.0+
#
# Feature Compatibility Matrix:
# | Shakapacker Version | Basic Pack Generation | Auto-Registration | Nested Entries | Async Loading |
# |-------------------|----------------------|-------------------|----------------|---------------|
# | 6.5.1 - 6.9.x | ✅ Yes | ❌ No | ❌ No | ❌ No |
# | 7.0.0 - 8.1.x | ✅ Yes | ✅ Yes | ✅ Yes | ❌ No |
# | 8.2.0+ | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes |
#
################################################################################
# components_subdirectory is the name of the subdirectory matched to detect and register components automatically
# The default is nil. You can enable the feature by updating it in the next line.
config.components_subdirectory = nil
# Change to a value like this example to enable this feature
# config.components_subdirectory = "ror_components"

# Default is false.
# The default can be overridden as an option in calls to view helpers
# `render_component` and `render_component_hash`. You may set to true to change the default to auto loading.
# NOTE: Requires Shakapacker 6.5.1+ for basic functionality, 7.0.0+ for full auto-registration features.
# See version requirements matrix above for complete feature compatibility.
config.auto_load_bundle = false

# Default is false
# Set this to true & instead of trying to import the generated server components into your existing
# server bundle entrypoint, the PacksGenerator will create a server bundle entrypoint using
# config.server_bundle_js_file for the filename.
config.make_generated_server_bundle_the_entrypoint = false

# Configuration for how generated component packs are loaded.
# Options: :sync, :async, :defer
# - :sync (default for Shakapacker < 8.2.0): Loads scripts synchronously
# - :async (default for Shakapacker ≥ 8.2.0): Loads scripts asynchronously for better performance
# - :defer: Defers script execution until after page load
config.generated_component_packs_loading_strategy = :async

# DEPRECATED: Use `generated_component_packs_loading_strategy` instead.
# Migration: `defer_generated_component_packs: true` → `generated_component_packs_loading_strategy: :defer`
# Migration: `defer_generated_component_packs: false` → `generated_component_packs_loading_strategy: :sync`
# See [16.0.0 Release Notes](docs/release-notes/16.0.0.md) for more details.
# config.defer_generated_component_packs = false

# Default is false
# React on Rails Pro (licensed) feature: When true, components hydrate immediately as soon as
# their server-rendered HTML reaches the client, without waiting for the full page load.
# This improves time-to-interactive performance.
config.immediate_hydration = false

################################################################################
# I18N OPTIONS
################################################################################
# Replace the following line to the location where you keep translation.js & default.js for use
# by the npm packages react-intl. Be sure this directory exists!
# config.i18n_dir = Rails.root.join("client", "app", "libs", "i18n")
#
# If not using the i18n feature, then leave this section commented out or set the value
# of config.i18n_dir to nil.
#
# Replace the following line to the location where you keep your client i18n yml files
# that will source for automatic generation on translations.js & default.js
# By default(without this option) all yaml files from Rails.root.join("config", "locales")
# and installed gems are loaded
config.i18n_yml_dir = Rails.root.join("config", "locales")

# Possible output formats are js and json
# The default format is json
config.i18n_output_format = 'json'

# Possible YAML.safe_load options pass-through for locales
# config.i18n_yml_safe_load_options = { permitted_classes: [Symbol] }

################################################################################
################################################################################
# TEST CONFIGURATION OPTIONS
# Below options are used with the use of this test helper:
# ReactOnRails::TestHelper.configure_rspec_to_compile_assets(config)
#
# NOTE:
# Instead of using this test helper, you may ensure fresh test files using Shakapacker via:
# 1. Have `config/webpack/test.js` exporting an array of objects to configure both client and server bundles.
# 2. Set the compile option to true in config/shakapacker.yml for env test
################################################################################

# If you are using this in your spec_helper.rb (or rails_helper.rb):
#
# ReactOnRails::TestHelper.configure_rspec_to_compile_assets(config)
#
# with rspec then this controls what yarn command is run
# to automatically refresh your Webpack assets on every test run.
#
end
```

Expand Down
32 changes: 32 additions & 0 deletions docs/core-concepts/webpack-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,38 @@ default: &default

The `bin/switch-bundler` script automatically updates this configuration when switching bundlers.

### Server Bundle Configuration (Shakapacker 9.0+)

**Recommended**: For Shakapacker 9.0+, use `private_output_path` in `shakapacker.yml` for server bundles:

```yaml
default: &default # ... other config ...
private_output_path: ssr-generated
```

This provides a single source of truth for server bundle location. React on Rails automatically detects this configuration, eliminating the need to set `server_bundle_output_path` in your React on Rails initializer.

In your `config/webpack/serverWebpackConfig.js`:

```javascript
const { config } = require('shakapacker');

serverWebpackConfig.output = {
filename: 'server-bundle.js',
globalObject: 'this',
path: config.privateOutputPath, // Automatically uses shakapacker.yml value
};
```

**Benefits:**

- Single source of truth in `shakapacker.yml`
- Automatic synchronization between webpack and React on Rails
- No configuration duplication
- Better maintainability

**For older Shakapacker versions:** Use hardcoded paths and manual configuration as shown in the generator templates.

Per the example repo [shakacode/react_on_rails_demo_ssr_hmr](https://github.com/shakacode/react_on_rails_demo_ssr_hmr),
you should consider keeping your codebase mostly consistent with the defaults for [Shakapacker](https://github.com/shakacode/shakapacker).

Expand Down
3 changes: 2 additions & 1 deletion lib/generators/react_on_rails/base_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ def copy_packer_config
puts "Adding Shakapacker #{ReactOnRails::PackerUtils.shakapacker_version} config"
base_path = "base/base/"
config = "config/shakapacker.yml"
copy_file("#{base_path}#{config}", config)
# Use template to enable version-aware configuration
template("#{base_path}#{config}.tt", config)
configure_rspack_in_shakapacker if options.rspack?
end

Expand Down
17 changes: 17 additions & 0 deletions lib/generators/react_on_rails/generator_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,21 @@ def add_documentation_reference(message, source)
def component_extension(options)
options.typescript? ? "tsx" : "jsx"
end

# Check if Shakapacker 9.0 or higher is available
# Returns true if Shakapacker >= 9.0, false otherwise
def shakapacker_version_9_or_higher?
return @shakapacker_version_9_or_higher if defined?(@shakapacker_version_9_or_higher)

@shakapacker_version_9_or_higher = begin
# If Shakapacker is not available yet (fresh install), default to true
# since we're likely installing the latest version
return true unless defined?(ReactOnRails::PackerUtils)

ReactOnRails::PackerUtils.shakapacker_version_requirement_met?("9.0.0")
rescue StandardError
# If we can't determine version, assume latest
true
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,25 @@ ReactOnRails.configure do |config|
# Set to "" if you're not using server rendering
config.server_bundle_js_file = "server-bundle.js"

# ⚠️ RECOMMENDED: Use Shakapacker 9.0+ private_output_path instead
#
# If using Shakapacker 9.0+, add to config/shakapacker.yml:
# private_output_path: ssr-generated
#
# React on Rails will auto-detect this value, eliminating the need to set it here.
# This keeps your webpack and Rails configs in sync automatically.
#
# For older Shakapacker versions or custom setups, manually configure:
# config.server_bundle_output_path = "ssr-generated"
#
# The path is relative to Rails.root and should point to a private directory
# (outside of public/) for security. Run 'rails react_on_rails:doctor' to verify.

# Enforce that server bundles are only loaded from private (non-public) directories.
# When true, server bundles will only be loaded from the configured server_bundle_output_path.
# This is recommended for production to prevent server-side code from being exposed.
config.enforce_private_server_bundles = true

################################################################################
# Test Configuration (Optional)
################################################################################
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ default: &default
# Location for manifest.json, defaults to {public_output_path}/manifest.json if unset
# manifest_path: public/packs/manifest.json

# Location for private server-side bundles (e.g., for SSR)
# These bundles are not served publicly, unlike public_output_path
# Shakapacker 9.0+ feature - automatically detected by React on Rails<% if shakapacker_version_9_or_higher? %>
private_output_path: ssr-generated<% else %>
# Uncomment to enable (requires Shakapacker 9.0+):
# private_output_path: ssr-generated<% end %>

# Additional paths webpack should look up modules
# ['app/assets', 'engine/foo/app/assets']
additional_paths: []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,22 @@ const configureServer = () => {
};
serverWebpackConfig.plugins.unshift(new bundler.optimize.LimitChunkCountPlugin({ maxChunks: 1 }));

// Custom output for the server-bundle that matches the config in
// config/initializers/react_on_rails.rb
// Server bundles are output to a private directory (not public) for security
// Custom output for the server-bundle
//<% if shakapacker_version_9_or_higher? %>
// Using Shakapacker 9.0+ privateOutputPath for automatic sync with shakapacker.yml
// This eliminates manual path configuration and keeps configs in sync.
serverWebpackConfig.output = {
filename: 'server-bundle.js',
globalObject: 'this',
// If using the React on Rails Pro node server renderer, uncomment the next line
// libraryTarget: 'commonjs2',
path: config.privateOutputPath,
// No publicPath needed since server bundles are not served via web
// https://webpack.js.org/configuration/output/#outputglobalobject
};<% else %>
// Using hardcoded path (Shakapacker < 9.0)
// For Shakapacker 9.0+, consider using config.privateOutputPath instead
// to automatically sync with shakapacker.yml private_output_path.
serverWebpackConfig.output = {
filename: 'server-bundle.js',
globalObject: 'this',
Expand All @@ -55,7 +68,7 @@ const configureServer = () => {
path: require('path').resolve(__dirname, '../../ssr-generated'),
// No publicPath needed since server bundles are not served via web
// https://webpack.js.org/configuration/output/#outputglobalobject
};
};<% end %>

// Don't hash the server bundle b/c would conflict with the client manifest
// And no need for the MiniCssExtractPlugin
Expand Down
Loading
Loading