React + non-SPA websites
Client-side React in non-SPA websites

React is a go-to for single page applications. Is it also the right choice for isolated JavaScript components within traditional, server-rendered websites like CMS-driven content sites?

When we consider rendering a React application, it’s easy to jump straight to thinking about single-page applications, starting off with an application bootstrap that looks something like this:

import React from 'react';
import ReactDOM from 'react-dom';
import config from './config';
import AppContainer from './AppContainer';
 
const appElement = document.getElementById(config.appElementId);
ReactDOM.render(<AppContainer />, appElement);

Code like the above is rendering a single React application into a page, and letting that app render and manage all the pages’s functionality. Not every website is a single page application, though.

Instead of forcing us to render one top-level component for a page and then making that responsible for everything within, React gives us the flexibility to mount multiple applications to different elements within the DOM on the same page. The page as a whole isn’t a React component, but it uses React to manage interactivity for small subsets of itself.

Many conventional, CMS-driven sites would benefit from using standalone React components for a few interactive components — navigation menus, modals, accordions, etc. What are the benefits and downsides when each page of a website renders a few smaller React components instead of using vanilla JavaScript views?

Benefits

  • Predictable state: For certain forms of UI functionality, declarative, state-driven functionality is efficient and understandable. React makes building interactive functionality more testable and predictable than with raw DOM manipulation.

  • Leveraging a cohesive set of libraries: Front-end developers haven’t had much of a UI library ecosystem since jQuery and similar platforms stopped being relevant. Communities like React’s, however, offer substantially cohesive sets of components that can be useful within the UI layer. Although relying on libraries for UI concerns isn’t always the best choice, having them available to handle small yet complex pieces of functionality — date pickers, for instance — can allow time and effort to be spent on more business-specific functionality.

  • Common architectural patterns: React supports multiple clear techniques to communicate across component boundaries. Building a component with React pushes us toward patterns that are common to the community and easily maintained by another developer. With non-SPA websites picking up more complex, interactive functionality as the web gains maturity, the benefits of building maintainable software through the use of common and easily-understandable patterns can’t be overstated.

  • Incremental adoption: If you’re not sure about React as a platform, adding a bit of React code to an existing page can allow for incremental migration toward more adoption of the platform with minimal risk — and allow you to back of that direction if your domain or team experience makes it less desirable once you’ve seen it in production.

  • Testing: Testing front-end code is hard to do well, and frequently overlooked. I’ve yet to have a more enjoyable experience implementing and debugging front-end tests than with React and some of the tooling commonly paired with it, such as Jest and Enzyme.

Downsides

  • Additional tooling expectations: There’s a certain overhead of polyfills and build tooling that arent’t strictly necessary within a vanilla ES2015+ JavaScript codebase, but that you’d be wasting productivity if overlooked when building for an idiomatic React codebase. Bringing on the dependency of React for an isolated view also includes promising to support that functionality.

  • Additional bundle weight and script startup time: A large bundle in itself isn’t that big of a deal. Sure, it’s nice to keep download sizes lean, but if a single page application requires a larger bundle weight, that’s the price of entry for higher interactivity. Unfortunately, outside of single page applications, a bundle must be parsed and executed more than once. Your application needs to bootstrap itself every time a user visits another page. In my case, this didn’t lead to an excessive performance penalty, but I’d recommend benchmarking performance for your use case. When it’s necessary to wring out more performance, a leaner React-like implementation that supports JSX can cut down on size, as can avoiding unnecessary polyfills based on understanding of your userbase.

  • Risk of neglecting progressive enhancement: As a result of choosing this approach, the effort to deliver an experience that works well without JavaScript may be neglected. I can imagine that without rigorous attention to detail, it can be difficult to support a JavaScript-free experience. Unfortunately, this risk isn’t unique to React — progressive enhancement takes effort to maintain even with vanilla JavaScript. Again, it comes down to developer familiarity with the tool paired with understanding of business and user needs.

Additional techniques

  • Multiple entry chunks: As a website gets larger and accumulates features specific to certain pages, using a buindler like webpack to emit multiple JavaScript entry chunks with the minimal code for an area of the website — whether a page or a section of many pages — can help thin out the initial overhead of each page load. Isolating a a complex page’s functionality into its entry chunk can significantly reduce the load across all other pages.

  • Code splitting: Alongside to having multiple entry chunks, code splitting these rather than serving each as a single file will minimize overhead in reloading the same code across multiple pages. Over the wire, the weight of React, polyfills, and other libraries — not to mention a site’s features — can add up.

Together, webpack documents these techniques as part of what they would call a multi-page application architecture: https://webpack.js.org/concepts/entry-points/#multi-page-application

Summarizing my own experience with React in non-SPA websites

In past projects, once I took care of the straightforward foundational work — providing mechanisms to find, mount, and initialize components, and providing the data necessary for them to potentially replace a static view of the same content — adopting React outside of a single page application has been an efficient and enjoyable experience. It’s saved me from reinventing the wheel on cross-component communication, and it encourages writing reliable, self-documenting components, particularly when paired with TypeScript and Jest.