React on Rails: How to use different versions of a file for client and server rendering

react on railsSeptember 20, 2022Dotby Justin Gordon

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 or main.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 and ServerOnly directory has a file Index.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.

Closing Remark

Could your team use some help with topics like this and others covered by ShakaCode's blog and open source? We specialize in optimizing Rails applications, especially those with advanced JavaScript frontends, like React. We can also help you optimize your CI processes with lower costs and faster, more reliable tests. Scraping web data and lowering infrastructure costs are two other areas of specialization. Feel free to reach out to ShakaCode's CEO, Justin Gordon, at justin@shakacode.com or schedule an appointment to discuss how ShakaCode can help your project!
Are you looking for a software development partner who can
develop modern, high-performance web apps and sites?
See what we've doneArrow right