RailsReactSSR
RailsReactSSR is a light weight JS server side rendering utility that takes advantage of Webpacker and NodeJS.
Motivation
In my latest project I designed my application to use Rails for my API endpoints and ReactJS with react-router to
handle routing and handle the front end. I needed a basic tool that would not add a lot of bloat, be able to handle
server side rendering while allowing me to process the response (i.e. handle redirects from the router) and did not
force me to use any packages or make decisions for me on how to structure my ReactJS code.
Dependencies
Installation
Add this line to your application's Gemfile:
gem 'rails-react-ssr'And then execute:
$ bundle install
Or install it yourself as:
$ gem install rails-react-ssr
Usage
RailsReactSSR::ServerRunner.exec!(bundle, props:, outputTemp:, max_tries:, delay:)
-
bundleis the path or name of the bundle in theapp/javascript/packsdirectory
(optional)
-
propsis a hash that will converted to a JSON plain object and passed to the server -
outputTempis either:- a boolean, where true will output the compiled server code to
tmp/ssr/[bundle].js - a string that is the full path to the file to write to
- a boolean, where true will output the compiled server code to
-
max_triesis the number of retries when fetching the bundle from tehwebpack-dev-server -
delayis the time in ms between each retry
Basic usage
server.js
// Some processing here
stdout(yourHtmlOutput);Your controller
def index
render html: RailsReactSSR::ServerRunner.exec!('server.js')
endPassing properties to the server
From the controller:
def index
render html: RailsReactSSR::ServerRunner.exec!('server.js', props: {current_user: current_user})
endFrom the server code:
...
// Do something with the user
console.log('Current user', serverProps.currentUser.username);
...The keys in the properties passed to the server will be transformed to camelized strings.
Handling redirects with React-Router-Web
Below is an example of handling redirects with react-router.
The principle should be the same for any routing packages.
server.js
// Not the complete story
const context = {};
const RedirectWithStatus = ({from, to, status}) => {
return (
<Route
render={({ staticContext }) => {
// there is no `staticContext` on the client, so
// we need to guard against that here
if (staticContext) staticContext.status = status;
return <Redirect from={from} to={to} />;
}}
/>
);
}
const markup = ReactDOMServer.renderToString(
<StaticRouter location={serverProps.location} context={context}>
<Switch>
<RedirectWithStatus
status={301}
from="/users"
to="/profiles" />
<RedirectWithStatus
status={302}
from="/courses"
to="/dashboard"
/>
</Switch>
</StaticRouter>
);
const output = {
html: markup,
logs: recordedLogs,
redirect: context.url,
status: context.status
};
stdout(JSON.stringify(output));More details on SSR and react-router at https://reacttraining.com/react-router/web/guides/server-rendering
Your controller
def index
output = RailsReactSSR::ServerRunner.exec!('server.js', props: {current_user: current_user, location: request.fullpath})
react_response = ActiveSupport::JSON.decode output.split(/[\r\n]+/).reject(&:empty?).last
react_response.deep_symbolize_keys!
if react_response[:redirect]
redirect_to react_response[:redirect], status: 302
else
render html: react_response[:html]
end
endCaching Example
To improve the response time from the server, you should consider caching.
Things to consider:
- Using a cache key that is not the same for every route if you are using a JS routing package.
- How large the response is form the JS server.
def index
## Do something to the path to generate a key that represents it in the server routes
cache_key = generate_cache_key_from_uri request.fullpath
output = Rails.cache.fetch cache_key, expires: 12.hours, race_condition_ttl: 1.minute, namespace: :react_server do
RailsReactSSR::ServerRunner.exec!('server.js', props: {current_user: current_user, location: request.fullpath})
end
handle_server_response output
endCommon Issues with SSR and Rails
I'm unable to execute code with webpacker-dev-server running.
The webpacker-dev-server injects a websocket when inline or hmr flags are set to true in for the dev_server
configuration in webpacker.yml. Make sure these are set to false if you plan on implementing SSR.
document or window is not defined
Global objects like document or window that are specific to browsers are not set when running the javascript on
the server; so it's best to wrap any code, or avoid using it outside of componentDidMount, componentDidUpdate or
componentWillUnmount.
Alternatives
There are several alternatives that are more comprehensive and might be a better fit for your use case:
Issues
Report bugs at https://github.com/jefawks3/rails-react-ssr. Please make sure to include how to reproduce the issue, otherwise it might be ignored.
Contributing
- Fork it (https://github.com/jefawks3/rails-react-ssr)
- Create your feature branch (git checkout -b my-new-feature)
- Commit your changes (git commit -am 'Add some feature')
- Push to the branch (git push origin my-new-feature)
- Create a new Pull Request
License
The gem is available as open source under the terms of the MIT License.