React and the Web Platform

Chris Vaszauskas

Staff Engineer

These days, the wisdom of the crowd in the React-o-sphere encourages engineers to adopt a great deal of complexity. Consider these blog posts by prominent members of the React community:

At AxisCare, we have been using React for nearly 10 years. We have ridden the wave from React.createClass to class components to function components to hooks and beyond. We have written single-page apps (SPAs), multi-page apps (MPAs), and React pages that feel more like vanilla HTML forms. But as time passes and our understanding of the capabilities of both React and the web platform evolves, we have found ourselves less and less aligned with the wisdom of prominent voices in the React crowd, especially when that wisdom pushes more and more complexity into our application.

Last summer, a blog post made the rounds called The Frontend Treadmill. The core idea is summarized nicely in this paragraph:

Companies that want to reduce the cost of their frontend tech becoming obsoleted so often should be looking to get back to fundamentals. Your teams should be working closer to the web platform with a lot less complex abstractions. We need to relearn what the web is capable of and go back to that.

Couldn’t have said it better myself. Our goal at AxisCare is to write the “best simple system for now” — the best system that meets our customers’ needs today as simply and cleanly as possible. When evaluating frontend technologies like GraphQL or React Server Components, we consistently find that these inherently complex, heavy-handed abstractions fight against the goal of fulfilling customer requirements as simply as possible. We have repeatedly found that the closer we get to the web platform, the simpler our frontends become, and the easier they are to understand and change.

So let’s start from first principles. Do we even need a JavaScript UI library in the first place?

Do We Even Need React?

At AxisCare, we use PHP-generated HTML for the “skeleton” of our pages, including things like the main and secondary navs. We use React for the “body” of our pages where most interaction happens. But the web platform is quite capable by itself. Do we really need a JavaScript UI library? Why not have the server generate all the HTML for each page, then sprinkle JavaScript where needed to enhance interactive controls?

At one point, one of our onboarding specialists noticed that our Payroll page was loading extremely slowly with very large datasets on her laptop. But it wasn’t just in the browser — other apps were freezing, too. It caused such a drag she couldn’t even get a recording of the issue because Zoom kept crashing. (You read that right: a performance problem in our web app was causing Zoom to crash.) This was obviously unacceptable.

We dug in and found that server performance was fine. The vast majority of the problem was happening client-side when the browser pegged the machine’s CPU to render 50,000 payroll records into one massive, server-generated HTML table. Behold:

100% CPU utilization for multiple minutes

That’s a screenshot of Chrome’s performance monitor on my M1 Macbook Pro after the browser desperately struggled for more than four minutes to render 1.4 million DOM nodes at 100% CPU utilization. On less powerful machines like our onboarding specialist’s Windows laptop, the browser crashed before this render completed, often taking down other running applications as well.

Now 50,000 records is a lot of data, but not a browser-destroying, Zoom-crashing amount of data. Users expect to be able to sort, summarize, filter, group, and search 50,000 records quickly (without expensive network roundtrips). Computers should be able to handle 50,000 records without breaking a sweat. So what was the problem? Some quick testing showed that the problem was not data volume, but DOM volume. Browsers can load 50,000 records into JavaScript memory with no trouble at all. Browsers cannot, however, load 50,000 records into one massive DOM table without significant performance problems (especially on less powerful machines).

Many of our newer pages are written in React, but the Payroll page hadn’t been updated in a while, and it was still using an earlier version of our reporting tooling based on PHP-generated HTML tables. We updated the Payroll page to use our React tooling, which virtualizes tables so a limited number of table rows are rendered into the DOM at once. The performance problem immediately vanished. Here’s a screenshot of the performance monitor for the same dataset on the React version of the page:

greatly reduced CPU and DOM utilization

Quite a difference! Less than a second of CPU time, two orders of magnitude fewer DOM nodes, and less memory usage even though we’re using a JavaScript library to control rendering.

So, do we need JavaScript? In this case, definitely: the vast majority of the performance improvement here comes from using JavaScript to page the table so that a constant maximum number of rows are rendered at once.

The HTML Performance and Complexity Cliffs

This anecdote illustrates what I have been calling the HTML performance cliff. Vanilla HTML has many significant advantages:

  • Leverages browser streaming to load blazingly fast™
  • Doesn’t require browsers to download and execute JavaScript correctly over an unreliable network connection
  • And critically, vanilla HTML encourages simplicity on the frontend

… but stray too far into territory like large datasets and you’ll quickly run into frozen browsers and frustrated users. As it stands today, the DOM has fundamental limitations that can only be overcome by JavaScript, meaning JavaScript unlocks scalability on the frontend that is orders of magnitude beyond the capabilities of server-generated HTML.

Since JavaScript is our only option to handle large datasets in the browser, the next question is which tool we should use to write JavaScript. In addition to the performance cliff, vanilla HTML also has a complexity cliff. Certain kinds of client interactivity are just easier to implement with a good component-based UI library. Think virtualized dropdowns, modal dialogs, client-side search widgets, and custom form controls like date pickers. If we had to implement all these use cases with nothing at our disposal but PHP-generated HTML and <script> tags, we’d quickly find that component-based UI libraries provide a superior developer experience for work like this. If we didn’t have a good JavaScript UI library in our toolkit, then we’d be stuck explaining to stakeholders why common UI patterns are prohibitively expensive for us to build and maintain.

After introducing React on the Payroll page above, we were able to quickly and easily implement client-side data-slicing features like virtualization, searching, grouping, and sorting — improving our users’ experience along dimensions of both performance and functionality. JavaScript (via React) unlocked not only functionality that would have been difficult to achieve otherwise, but also scalability that would have been impossible to achieve otherwise.

With Great Power Comes Great Responsibility

So we use React. But using React opens Pandora’s box. Many of the worst frontend fails in recent years involve hilariously unnecessary JavaScript payloads. (See Alex Russell’s Reckoning series.) In that light, one of the greatest advantages of vanilla HTML is that it naturally discourages the ever-present temptation to shove complexity into the frontend. Is there a way to keep that benefit?

The sweet spot for us at AxisCare is React, but as close as possible to the web platform. We write the skeleton of all our pages in PHP-generated HTML (including the main and secondary navs), but we write the body of our pages in React so we can flex smoothly with large datasets and client-side interactivity requirements. We intentionally avoid supporting JavaScript technologies that increase frontend complexity:

  • A client-side network cache like TanStack Query or Apollo? Just pass props from the server into the root component.
  • A state management library like Redux? Just use useState.
  • A form management library like React Hook Form? Just use web platform features (with some basic useState-based state management if needed).
    • The web platform provides mature tools to manage forms and we find that many of our forms don’t need React-based state management at all
  • API calls via Axios or fetch? Just use form submissions and HTTP redirects.
  • A client-side router like React Router? Just use links and server routing.
  • Server rendering React components + client hydration? Just make the backend faster.
    • React server rendering incurs significant UX tradeoffs like the “uncanny valley” where UI is present but not yet hydrated and interactive
    • Server rendering also often trades faster LCP for slower TTI/INP, giving users “one app for the price of two”
    • For us, lowering TTFB via traditional backend optimizations is often a higher-leverage change than server rendering our frontends
  • React Server Components (RSCs)? Just use client-side rendering.

The web is a singularly capable software delivery platform with significant built-in capabilities, and our web app is best (and our code simplest) when we’re leveraging those capabilities rather than building inferior versions of them ourselves in JavaScript. We aim to find the sweet spot where we are using both React and the web platform where they most complement each other’s strengths.

It Depends

Are there any exceptions to these guidelines? Of course! As with all things in the software world, it depends. The above reasoning applies to the vast majority of the pages in our web app, but not all of them. Broadly, the pages in our web app fall into two categories:

  • Server-driven pages: pages like a caregiver profile or a user management screen. These pages are short-lived, mostly stateless, and well-suited to web platform primitives like form submissions and redirects.
  • Client-driven pages: highly interactive pages where users are performing complex, domain-specific actions. These pages are long-lived, stateful, often real-time, and ill-suited to web platform primitives like form submissions and redirects.

In our system, agencies schedule visits between seniors and caregivers. We provide a live schedule view where users can view the whole schedule for an entire week. The page provides many behaviors that cannot be implemented without additional frontend complexity:

  • Real-time updates as other users make changes
  • Drag-and-drop rescheduling on a calendar grid
  • Client-side search, filtering, and summarization
  • And several domain-specific actions with significant client-side behaviors

The web platform and vanilla HTML provide much of the functionality we need for server-driven forms like senior profiles, but client-driven pages like the live schedule are not well-suited to web platform operations like form submissions and redirects since it would be prohibitively expensive to reload the whole live schedule after every change. On these client-driven pages, we turn to supporting technologies like:

  • Redux for state management
  • TanStack Query to cache network calls
  • Other third-party libraries to help with things like printing, Excel exports, certain UI patterns, etc.

These supporting technologies are too heavy-handed for simple forms, but provide simplifying abstractions when building client-driven pages like the live schedule. We have maybe 4-5 pages in our web app that merit extra client-side tooling. The vast majority of our pages, however, are server-driven forms where we intentionally avoid extra tooling and abstractions and stay as close to the platform as possible.

The Sweet Spot

At AxisCare, we strive to use both the web platform and React where they are strongest: the web platform for routing, forms, and links — and React for scalability and client-side interactivity. We intentionally resist the complexity of heavy-handed JavaScript tooling by writing React that is as close as possible to the web platform. We make occasional exceptions for highly interactive, real-time pages, but most of the time, we find that our web app is simplest and cleanest when React and the web platform complement each other’s strengths.

Want to work with us?

We hire great people, invest in their growth, and trust them to do great work. No egos here. Just a handful of humble, talented, and conscientious builders who play well with others and work as a team to ship great things.

© 2025 AxisCare. All rights reserved.