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.
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:
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:
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:
The second table focuses on production experience and captures:
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.
As usual, there is no right or wrong answer — it all comes down to needs and personal preferences.