There is such a rich, user-driven ecosystem around React that it can be difficult to discern which frameworks, if any, should be used to develop enterprise-grade applications. This blog post dives into the most popular ones and analyzes which are most suitable for that specific use case.
Before diving in, please remember: the decision of which one is the right one for a given organization or application is very context-specific, so diligent consideration is required to ensure an optimal outcome.
The React community has eschewed using a framework for most of its lifetime. React has always been billed as the antithesis of frameworks like Angular, with the thought that React, as a library, allows for a significant level of customization with a lower barrier to entry. A benefit of this philosophy is that each React project gets tailored to the needs and opinions of those implementing it.
This heterogeneous environment has left some with the feeling that life could be simpler if only there was a consistent standard that we all could adhere to. No two developers or architects agree on 100% of the millions of little decisions that are made throughout the course of an application’s development, and this has led to a varied and non-standard landscape.
To that end, there have been many attempts to enforce some semblance of a framework on top of React that, if implemented to the spec, would yield more predictable and consistent code and results than permutations of vanilla React.
We at Keyhole have seen numerous different React implementations on engagements we’ve been involved in. None of them are “wrong,” per se. They all have their benefits and drawbacks. We as thought leaders in the enterprise AppDev space are always looking for trends and patterns that will become the norm so we can help guide current and future organizations to make effective decisions. The time has come to seriously look at React frameworks as a valid alternative to vanilla React.
Don’t just take our word for it. This is also the opinion of one of the core maintainers of the React library at Meta:
Defining a React “Framework”
For the purposes of this blog post, something is a React Framework if it is considered a React Framework by the React community. Perception is reality… or so the saying goes.
There are a few objective criteria that can be universally agreed on to rank one piece of software over another. That is why this evaluation includes objective measures (popularity, number of maintainers, frequency of revisions, and bug fixes) along with subjective measures (anecdotes and community consensus).
Again, there is no “right” or “wrong” React framework. Each one has its own benefits and drawbacks. What is suitable for one project or organization may be wrong for another, even in the same industry.
Intended Use Case
Enterprise-grade SPAs are fundamentally different in nature and usage than other ways to use React. For instance, an enterprise application is a B2B or B2C that has little to no concern for considerations like SEO. They are typically used on networks of extremely high availability and bandwidth and on homogenous machines that are relatively high-powered.
On the other hand, using React to power a website that can be viewed by anyone across the world, from the Americas to sub-Saharan Africa, on desktops and tablets and mobile phones, on broadband all the way down to 3G, requires a significantly different set of considerations and requirements than an enterprise web app.
In my research, most React frameworks are geared more toward static site generators or React Native use cases rather than the intended use case as described above. I found only a limited number of NPM packages that fit the bill. As a disclaimer, this was not poisoning the well but possibly the result of there not being a lot of battle-tested choices in the enterprise space.
One hypothesis I have is that, in enterprise development, there is a tendency to err on the “build” side of the “build vs buy” paradigm or in this case, “build vs use.” There are full-time developers and architects whose job is to make an application or a set of applications successful. They are able to devote more time and attention to the TLC needed to make a large application successful, more so than weekend warriors that maintain countless third-party libraries that we all depend on.
This hypothesis would likely explain why so few fit the bill. There simply isn’t a high demand for an enterprise React framework like there is for Angular.
Caveats over with, the list of React frameworks that fit within the intended use case are (in no particular order):
After compiling that list, I put them into NPM Trends to get a feel for community adoption and usage. As anticipated, the results weren’t even close.
Next.js is far and away the favorite in the community. Going into this blog post, it was the one that I heard the most about and was most familiar with. Although, I was surprised to see it so far ahead of everything else. The second-place winners were Chakra and Gatsby, but they have a slightly different use case in terms of websites than Next.js does with web apps.
As the clear-and-away favorite, a majority of the rest of this blog post will be focused on Next.js. Setting up a new sample application with Next.js is fairly straightforward by following their tutorial.
It gives many of the basic features that CRA (create-react-app) gives, including automatic compile and bundle, fast refresh, and static file serving. It also gives SSR (server-side rendering), but as stated previously, this is not as big of a deal for the desired use case.
What sets it apart immediately is the notion of pages that correlate to routing. This is something that usually requires an additional library to be installed, likely react-router-dom and other supporting libraries. Then, the decision needs to be made if the routing will be a client-side hash or not, which then impacts the Navigation API for the forward/backward buttons, etc.
Taking routing out of my hands felt strange at first, but it was understandable. As a framework, Next.js would prefer a consistent setup across applications rather than each application’s architect taking it upon themselves to impose a bespoke structure that may or may not be present in others. Next.js does allow for dynamic as well as shallow routes out of the box, which helps with some of the time it usually takes to scaffold a React application with routing.
The next feature I ran into is pre-rendering. This is done ostensibly to improve client-side performance as well as SEO. This might have some minor benefits for enterprise-grade SPAs in terms of performance, but SEO is not a consideration in our use case.
The pre-rendering can be done with static generation or server-side rendering. A hypothetical application by a medium to a large organization would be presented on the UI with a heavy dependency on the current user’s permissions and roles, as well as data dynamic to the screen they’re on. There may be areas of the application that could be statically generated, but those are few and far between.
The SSR may also help users on older devices. Although, that is usually a leading indicator that an organization needs to invest in better hardware to increase the efficiency of end users. It is a mitigating factor at best and should not be primarily or secondarily relied on for performance improvements.
Client-side rendering is more than likely what a majority of enterprise applications will use to interact with the API layer. Next.js’s opinion is that, in this mentality, the SWR (stale-while-revalidate) library should be used. It allows for a fast initial response of the most recently fetched (stale) data while fetching more timely data (revalidating) happens in the background.
This mentality fits well with React’s state lifecycle and hooks, but most large applications would opt to only show the user the freshest data possible or show them nothing at all. For instance, a banking application should never intentionally be designed to show the user an incorrect or misleading account or current balance. People are very picky about their checking accounts, so a stale version of the data being presented to a customer would need to be very well explained to ensure a positive experience.
There is some support for native API requests to be handled with Next.js, as files inside
pages/api/ are mapped to
/api/ and evaluated on the server. However, almost all large-size enterprise applications are built once as a set of static assets, then deployed via Docker images, with the same Docker image being promoted up the environments. This does not lend itself well to having a completely separately deployed API, be it REST or GraphQL or something else, that the UI would ultimately hit to grab and interact with the data.
CSS support is also a first-class feature in Next.js. Honestly, this felt very odd to me, having spent the last few years using Material UI and styled components. CSS can be problematic in a large-scale application built of loosely coupled, highly cohesive components that should interact with each other as little as possible.
There have been some advancements on that front with component styles, even if it is only a design pattern that ensures the styling of components does not bleed to their peers or ancestors. I am still on the fence about how I feel about this implementation in Next.js instead of what I am familiar with and have seen work at scale.
On that note, most applications I’ve worked on have already included some parallel patterns of Next.js, including pages and layouts. There are only so many different reliable ways to build a large application, and it’s nice to see some familiar concepts. Image and font optimization, static files, and fast refresh are usually standard setup with any project that uses Webpack, though Vite seems to be picking up some speed in that area. ESLint is also standard on almost every project I work with.
It’s not just the Intellisense that it provides or the ease at which refactoring can be done through IDE-assisted tools. Since the OO paradigm shift, almost all programmers think in terms of classes and relationships and, ultimately, data. All applications that we develop, almost without exception, are meant to transfer data from cold storage to the user, allow the user to modify it, then send it back to cold storage.
In my experience, TypeScript proves to add another layer of complexity on top of an already complex ecosystem, with little to no long-term benefit. Eventually, everything becomes an
any type, completely defeating the point of TypeScript.
I admit that I am in the minority in terms of UI architects in this opinion and that I could be wrong. So I welcome anyone reading this blog that disagrees to reach out to me and convince me otherwise!
I then looked at some of the different authentication/authorization patterns provided by Next.js. Passport seems to be one of the most commonly used options on the JS side, but MSAL does have React wrappers to make it easier to integrate with. Next.js supports a variety of different options, ranging from Auth0 to Userbase.
This makes sense because, as a framework, it needs to integrate well within the security ecosystem of the organization that’s using it. Security is very heterogenous between companies, so the authentication/authorization layer needs to be flexible.
Next.js also supports a variety of different automated testing frameworks. I am somewhat in the minority in my opinion on testing enterprise applications. I believe that the proper level of testing depends on who is the “user” of the code is. If the JS that’s produced will primarily be used as a library and the “user” is other developers, then having an extensive automation suite of unit tests makes sense.
If the “user” of the code is an actual end user of the application, then spending more resources and time developing a robust set of automated E2E tests with something like Selenium is more worthwhile than brittle unit tests that need to be tweaked as the presentation layer changes. Next.js does not take a strong stance either way, and it has support for some of the common testing libraries like Cypress and Jest.
There are a whole host of different advanced features that Next.js provides support for out of the box that I have found myself using in the large React projects I’ve worked on. Module path aliases, CI build caches, error handling, source maps, as well as custom security headers on HTTP requests are the most common that I utilize in the Next.js experience.
Next.js offers a battle-tested and well-thought-out structure to develop a React application on top of. It was the only true contender for a React framework that I came across. I strongly encourage those embarking on writing a new React application to consider Next.js before rolling their own scaffolding.
As with most options, there are some features that don’t apply to every project and would need to be turned off in some situations. However, using Next.js is a very viable option and could potentially help speed up the initial setup of a React application.