27th May 2021
A look into React tooling beyond create-react-app
Setting this up manually usually requires configuring and running a module bundler capable of transpiling and bundling all the modules together. Webpack and Babel are popular, widely used tools in this category. Tools such as create-react-app put all of this together to provide a seamless development experience, including Hot Module Reload for fast iteration during development and all the goodies we’re familiar with.
You can find all the source code accompanying this article at nearform/web-dev-perf.
create-react-app (CRA) has been a personal favourite for a long time. The effort of setting everything up from scratch is hard to justify when you can simply run a command in the terminal and have a fully working application in a matter of seconds.
npx create-reate-app myApp
Besides, CRA has evolved to include more features and customisation options over time, and it also brings together many best practices that are expensive to reproduce when starting from a blank slate. Finally, when you need ultimate customisation, you can eject and get access to all the scaffolding that CRA would otherwise hide from sight. The downside of ejecting is that it is destructive and cannot be undone.
CRA is easily the most widely used and mature React development stack. It is also the most conservative: It supports very old browsers like IE9 (via polyfills), and everything is entirely bundled, both during development and for production builds.
At a high level, it does this by using Webpack and Babel to create a bundle of the application that is compatible with all the supported browsers. This means that all the source code of the application and its dependencies are bundled together (or in multiple chunks), which are then served to the browser.
Creating these bundles is an expensive operation that takes place in memory at development time to speed up the development experience without hitting the file system.
Nevertheless, much of this work is unnecessary because the browsers we use for development support features like es modules (ESM), which make bundling and transpilation largely unnecessary. CRA doesn’t leverage this but newer development stacks fill this gap.
Vite can be thought of as a CRA on steroids, built to leverage modern browser features and the latest build tools.
You start a new React app with Vite with a simple command:
npm init @vitejs/app myApp --template react
In the scope of a React application, the main difference between Vite and CRA is that Vite uses native browser support for ESM, thereby avoiding the need to do much of the bundling that CRA does. The upside of this is that development builds are much faster, leading to lower startup and refresh times.
In practice, some bundling still happens in order to support features that are not available in browsers, such as bare modules. This is done using esbuild, an extremely fast module bundler.
For production builds, Vite can still use a module bundler to support browsers without native ESM support. Whereas CRA uses Webpack, Vite uses Rollup.
As well as an optimised development experience, Vite also provides additional features not available in CRA or other more mature frontend frameworks:
- Glob imports
- Support for Web assembly
- Support for monorepos and linked dependencies
- Multi-page apps
- Support for building libraries via library mode
CRA and Vite are far from being the only two options for building React apps. Other notable mentions go to Next.js and Snowpack, and there are others. The comparison between Next.js and Snowpack is even broader than that between CRA and Vite.
Next.js is a mature React framework with a host of features, including an entire server application to support Server Side Rendering of the Next.js application. In this regard, Next.js’s feature set is a superset of CRA and Vite.
Whereas the previous two provide only the basic scaffolding for building React applications, Next.js provides pre-made design decisions about routing, styling and more. Overall, Next.js is more powerful by design and it’s also more opinionated because many choices have been made on your behalf.
Snowpack is similar in scope to Vite in that it uses modern browser features such as ESM and tools like esbuild. Because of that, Snowpack is blazingly fast, but it offers just a fraction of the features that Next.js does. On the other hand, its output is highly optimised in terms of file size. It makes fewer upfront decisions than Vite does, which means it’s more flexible but less featured because those decisions are left to the user.
Comparing the full feature sets of the aforementioned React development stacks is outside the scope of this article. We focus on measuring the performance in terms of development speed and output efficiency.
The repository accompanying this article contains a suite of benchmarks and is accessible at nearform/web-dev-perf. The repository documentation explains in more detail what is measured and how to interpret the output of the benchmarks.
In order to make the comparisons reliable, all the development stacks implement the same React application, which consists of a simple Todo list. The original source code of the Todo list example application is available at gabrielsanttana/react-todolist.
The benchmarks can be run on your local machine and are also available as output of the CI process on the hosted repository. They capture various statistics about the development and production experience of each of the development stacks.
The first table focuses on development experience and captures:
- startup time (cold start) of the development build
- min/mean/99th percentile/max time between the modification of a source file and the result appearing in the browser during development
The second table focuses on production experience and captures:
- build time of a production build
- size of the output of the production build
Note: When comparing Next.js with other stacks, it should be noted that Next.js outputs a server side application as well, which others don’t.
How to choose a React development stack
As usual, there is no right or wrong answer — it all comes down to needs and personal preferences.
- Next.js is the most heavyweight and opinionated of the development stacks, but it also comes with many features not available anywhere else (e.g. SSR). Its development and build times are slower than most other tools but are comparable — and in some cases better — than those of CRA. Next.js is a good option if you like decisions to be made on your behalf and need an easy way to set up SSR.
- CRA is the mature, frontend-only option that relies on battle tested tooling like Webpack and Babel. It’s a good option if you want to ensure the widest compatibility with older browsers and don’t mind compromising a little on development speed for that purpose.
- Vite is a great option for the CRA lovers who want to benefit from the higher development speed and newer feature set of a more modern development stack and are not afraid of the limitations of a relatively young stack.
- Snowpack is the extreme approach to React development that focuses entirely on modern tooling and leaves much of the rest of the work to the user. If speed and cutting edge tooling are your priorities and Vite doesn’t give you enough of those, Snowpack may be the tool you’re looking for.