React on Rails: How to use different versions of a file for client and server rendering
React on Rails is one of the gems used for both client-side and server-side rendering. Integrating the gem for the client or server-side rendering is pretty straightforward.
The below code renders the component only on the client side.
<%= react_component("Post", props: { posts: @posts }) %>
The component gets rendered on the server side
when passing the prerender
option as true
.
<%= react_component("Post", props: { posts: @posts }, prerender: true) %>
We need to make sure to register this component with ReactOnRails.
import ReactOnRails from 'react-on-rails';
import Post from './Post';
ReactOnRails.register({ Post });
The above case is an example when we use the same component for both client and server rendering. There can be times when our application might require different files or code for client vs server rendering.
Let's explore three such approaches:
Using different entry points
With multiple entry point support in Webpack, applications have the ability to configure the entry points. This feature can be used in the case of client vs server rendering. We can define one entry point for the client and one for the server.
We thus need to specify two different entry points in our React on Rails configuration too. Our client entry will look as below:
import ReactOnRails from "react-on-rails"
import App from "./ClientApp"
ReactOnRails.register({ App })
And our server entry, typically configured as:
config.server_bundle_js_file = "server-bundle.js"
will look like this:
import ReactOnRails from "react-on-rails"
import App from "./ServerApp"
ReactOnRails.register({ App })
Note:
- This functionality will work for applications where we differentiate
the entry points at the top level.
When working with the React-Router app,
we can specify different entry points in our
index.js
ormain.jsx
file.
Conditional code that can check if window is defined
The simplest approach to change the code within a file is to check
the window global variable
.
The option is present only on the client (browser) side,
this is the simplest way to check if we need to render client or server-side code.
// window should be falsy on the server side
if (typeof window === 'undefined') {
runClientCode()
} else {
runServerCode()
}
Using Webpack Resolve Alias in the Webpack Config
We can use
WebPack Resolve Alias
to import
or
require
certain modules easily
when the file needed is not a top-level entry point.
In other words,
just using a different entry point won't work in this circumstance.
This is useful where the prior example of configuration via a simple
conditional is not convenient.
Note, this configuration is uncommon.
However,
it's worth knowing about in case you get circumstances where the conditional is inconvenient.
We can configure our resolve aliases:
Use different resolutions for the same file
function setResolve(webpackConfig) {
// Use a different resolution for Client and Server file
let JsFilePath;
if (process.env.SERVER_BUNDLE_ONLY) {
JsFilePath = path.resolve(__dirname, "../bundles/JsFileServer");
} else {
JsFilePath = path.resolve(__dirname, "../bundles/JsFileClient");
}
const resolve = {
alias: {
JsFile: JsFilePath,
...
...
},
}
process.env.SERVER_BUNDLE_ONLY
is an environment variable that
should be set to YES if we want server-side rendering.
To know more about this configuration please check our
React on Rails Demo with SSR and HMR.
We load either the client or server file based on the serverRendering
configured by resolve.alias
.
We can import the JsFile as below
and
the build knows which file path to use:
import JsFile from 'JsFile';
Use different resolution for the right directory of client or server files
Sometimes you will want to configure a directory dynamically rather than a file.
Use the resolve.alias.variant
option.
-
Update
webpack/set-resolve.js
as below:function setResolve(webpackConfig) { // Use a different resolution for Client and Server file let variant; if (process.env.SERVER_BUNDLE_ONLY) { variant = path.resolve(__dirname, "../bundles/variant/ServerOnly/"); } else { variant = path.resolve(__dirname, "../bundles/variant/ClientOnly/"); } const resolve = { alias: { variant: variant, ... } }
-
Add the different client and server versions to the respective
bundles/variant/<Client/Server>Only
directories. -
Let's say the
ClientOnly
andServerOnly
directory has a fileIndex.js
. We can import the file as below:import Index from 'variant/Index.js'
Based on the
serverRendering
option the index file of the client or server directory will be loaded.