RailsReactSSR is a light weight JS server side rendering utility that takes advantage of Webpacker
and NodeJS
.
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.
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
RailsReactSSR::ServerRunner.exec!(bundle, props:, outputTemp:, max_tries:, delay:)
bundle
is the path or name of the bundle in theapp/javascript/packs
directory
(optional)
props
is a hash that will converted to a JSON plain object and passed to the serveroutputTemp
is 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_tries
is the number of retries when fetching the bundle from tehwebpack-dev-server
delay
is the time in ms between each retry
// Some processing here
stdout(yourHtmlOutput);
def index
render html: RailsReactSSR::ServerRunner.exec!('server.js')
end
def index
render html: RailsReactSSR::ServerRunner.exec!('server.js', props: {current_user: current_user})
end
...
// 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.
Below is an example of handling redirects with react-router
.
The principle should be the same for any routing packages.
// 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
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
end
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
end
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.
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
.
There are several alternatives that are more comprehensive and might be a better fit for your use case:
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.
- 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
The gem is available as open source under the terms of the MIT License.