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 websites?

I’ve recently found myself in a few situations where using React — or a more lightweight alternative, like Preact — in a server-rendered, non-SPA website was beneficial.

When we think about rendering a React application, it usually looks something like this:

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

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 of mounting multiple components 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?


  • Common architectural patterns: 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 communicating intent through the use of common and easily-understandable patterns can’t be overstated.

  • Predictable state: For certain forms of UI functionality, declarative, state-driven functionality is beautifully efficient and understandable.

  • Leveraging a cohesive set of libraries: Ever since the demise of the jQuery plugin, front-end developers haven’t had much of a shared ecosystem within which to share libraries of common functionality. Ecosystems like React, however, offer substantially cohesive sets of components that can be incredibly useful within the UI layer.


  • 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 foolish to overlook when catering to an idiomatic React codebase. Bringing on the dependency of React for an isolated view also includes the peripherals of that ecosystem.

  • 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 it’s easy to imagine a scenario where it would.

  • Abandoning progressive enhancement: As a result of choosing this approach, the effort to progressively enhance our components turned from making sure the initial HTML and CSS worked without JavaScript to building alternative content for users and bots without JavaScript enabled. I can imagine that without rigorous attention to detail, the alternative functionality would fall out of date. Unfortunately, this risk isn’t unique to completely separate alternative functionality — progressive enhancement takes effort to maintain even with vanilla JavaScript.

Related articles

Beyond allowing the await keyword to be used within them, JavaScript’s async functions have their own useful aspects. As an alternative to Promise.resolve() and Promise.reject(), consider using async functions to cleanly wrap return values in Promises for convenient mocks in your test suite and to ensure consistent return types.


How to integrate build-time or server-side syntax highlighting for markdown code fences with two libraries: markdown-it and Highlights (Atom’s syntax highlighting engine).


A Firefox issue where right click on a button results in a click event listener firing because of event delegation performed automatically by a JavaScript SPA framework.