Editor’s note: This post was updated on 28 July 2022 to reflect the current versions of Next.js and Create React App, compare performance differences in image optimization and code splitting, review some Next.js limitations, and provide information about migrating between Create React App and Next.js.
Next.js vs. Create React App: What’s the best way to improve single-page application performance? In this article, we will unpack that question with some data. But first, we need to understand what exactly we are comparing.
We will cover:
The Replay is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
The first step towards understanding the performance differences between Next.js and Create React App is to understand the frameworks themselves. Let’s learn a little more about each option and define important terms such as SSR and CSR.
Next.js is a React framework built by Vercel (formerly Zeit) to make server-side rendering (SSR) easier for React applications regardless of where your data comes from.
Next.js also supports static exporting and, newly, incremental static regeneration. However, for the purposes of this post, we will focus on its server-side rendering (SSR) capability.
Create React App is officially supported by Meta for creating single-page applications (SPAs) for React. Again, for the purposes of this post, we are paying attention to the term “single-page.”
Next.js is one way that you can leverage React to support server-side rendering (SSR). Likewise, Create React App is one way that you can leverage React to support client-side rendering (CSR).
There are other frameworks out there when it comes to either choice, but what we are really comparing in this post is how each rendering strategy impacts web application performance. We just happen to be using two of the more popular frameworks out there to make that comparison.
Let’s start our experiment with just one of many other questions we can ask when comparing Next.js vs. Create React App: Does SSR improve application performance?
Walmart Labs published a great post titled, “The Benefits of Server Side Rendering Over Client Side Rendering.” They also provide some excellent diagrams that demonstrate the fundamental difference between SSR vs. CSR performance.

Remember, you can support SSR in your React applications with frameworks like Next.js, while frameworks like Create React App enable you to support CSR.

These diagrams postulate that SSR can deliver HTML to the browser faster than CSR can. So, let’s hypothesize that a web application built with SSR is more performant than one built with CSR.
Note that the Walmart Labs article calls out synchronous SSR as a bottleneck with renderToString. Starting with React 16 and Fiber enhancements, this is no longer an issue.
The best way to test our hypothesis is by building two applications with identical functionality and UI. We want it to mimic a real-world application as much as possible, so we will set a few parameters.
The application must:
Software developers are typically spoiled with high-powered computers paired with blazingly fast office networks; we do not always experience our applications the same way our users do.
With that in mind, when optimizing for performance, it is important to consider both network and CPU limitations. Mobile devices generally have less processing power, so heavy JavaScript file parsing and expensive rendering can degrade performance.
Fortunately, Chrome provides a dev tool called Lighthouse, which makes it easy for us to step into the shoes of our users and understand their experience. You can find this under the “Lighthouse” panel in Chrome DevTools.

We will use the exact settings displayed above:
If you live in Northern California, you are likely on servers living in AWS Region us-west-1 (N. California) all day. In that case, you are not experiencing your application the same way as users in other parts of the United States, nor other parts of the world.
So, for the purposes of this test, the demo apps and the API were deployed to Sydney, Australia (specifically, Vercel’s syd1 region). The client’s browser will be accessing the applications from Boulder, CO, USA.
The distance between Boulder and Sydney is 8,318 mi (13,386 km).

Look at what that means for data fetching between these two applications:

The image above illustrates the impact of geographical distance on CSR data fetching. The image below illustrates the same, but for SSR data fetching:

In order to avoid Chrome extensions negatively affecting our app’s page load performance, we’ll analyze its performance in incognito mode.
The code for the two apps is available in my GitHub. You can view the result of each application at the links below:
All of the code is in a monorepo:
/cra contains the Create React App version of the application
/nextjs contains the Next.js version
/api contains a mock API that both applications use
The UI appears identical. Remember that the Create React App application has a CSR UI, while the Next application has an SSR UI:


And the JSX is nearly identical. See the Create React App code below:
// Create React App
<ThemeProvider>
<div>
<Text as="h1">Create React App</Text>
<PrimaryNav align="left" maxItemWidth="20rem">
<NavItem href="/" selected>Create React App</NavItem>
<NavItem href="/nextjs">Next.js</NavItem>
</PrimaryNav>
<Table
data={users}
rowKey="id"
title="Users"
hideTitle />
</div>
</ThemeProvider>
Now compare it to the Next.js code here:
// Next.js
<ThemeProvider>
<div>
<Text as="h1">Next.js</Text>
<PrimaryNav align="left" maxItemWidth="20rem">
<NavItem href="/">Create React App</NavItem>
<NavItem href="/nextjs" selected>Next.js</NavItem>
</PrimaryNav>
<Table
data={users}
rowKey="id"
title="Users"
hideTitle />
</div>
</ThemeProvider>
We will get to what the ThemeProvider and other components are in a moment.
Notice that while most of the code above is the same for both Create React App and Next.js, the code differs in how the data is fetched from the API. See the Create React App code below:
// Create React App
// This all executes in the browser
const [users, setUsers] = useState([]);
useEffect(() => {
const fetchData = async () => {
const resp = await axios.get('/api/data');
const users = resp.data.map(user => {
return {
id: user.id,
FirstName: user.FirstName,
DateOfBirth: moment(user.DateOfBirth).format('MMMM Do YYYY'),
}
});
setUsers(users);
};
fetchData();
}, []);
And the Next.js code looks like so:
// Next.js
// This all executes on the server on first load
Index.getInitialProps = async({ req }) => {
const resp = await axios.get(`http://${req.headers.host}/api/data`);
const users = resp.data.map(user => {
return {
id: user.id,
FirstName: user.FirstName,
DateOfBirth: moment(user.DateOfBirth).format('MMMM Do YYYY'),
}
});
return { users };
}
getInitialProps is a special function that Next.js uses to populate the initial data for a page in Next.js. You can learn more about fetching data in the Next.js docs.
So what’s with all these components, and why are we using Moment.js?
We are also pulling in Moment.js because it is a larger dependency that adds some JavaScript weight and also some additional processing that needs to occur when rendering the component tree.
The actual libraries that we’re using are not important; the point is to get a little closer to the weight of a normal application without taking the time to build all of that in its entirety.
Here are the Lighthouse results for a full page load on our Create React App (CSR) application:

And below, you can see the Lighthouse results for a full page load on our Next.js (SSR) application:

Based on the overall score, you can see that our Next.js application (SSR) performs better than our Create React App (CSR) application. To understand the details of these metrics, read the Lighthouse Scoring Guide.
One of the more notable differences for our purposes is the First Meaningful Paint. See the difference below:
According to Google’s First Meaningful Paint docs, this metric “identifies the time at which the user feels that the primary content of the page is visible.”
Lighthouse also helps us visualize these differences. Check out this comparison between Create React App (CSR, top) and Next.js (SSR, bottom):


Do the visuals above look familiar? They should, because they mimic the diagrams we looked at previously, when we postulated that SSR can deliver HTML to the browser faster than CSR. Based on these results, it can!
To view the Lighthouse results yourself, follow these steps:
When it comes to image optimization, CRA does not have much to offer. Next.js once again outperforms CRA in this case.
Next.js has a built-in image component called next/image, which is a modified HTML <img> element with a number of built-in performance optimizations for rendering images. It was introduced in version 10.
The next/image component serves device-specific images using modern image formats. It also speeds up page loading by loading images only when they enter the viewport, with blurred-up placeholders as an option.
When it comes to performance, code splitting is another important factor to think about.
Splitting the application’s bundle into smaller portions that each entry point needs is known as code splitting. The goal is to reduce the initial load time of the application by loading only the code needed to run that page.
In order to perform code splitting properly with Create React App, the developer must put in a lot of effort and use additional libraries. It’s advisable to stick to the dynamic import() syntax because it’s the recommended technique for managing code splitting in Create React App, and Webpack will immediately start code-splitting your app when it encounters it.
In Next.js, each file in the pages/directory is automatically code-split into its own JavaScript bundle during the build stage; therefore, code splitting is enabled out of the box. Additionally, any code that is shared between pages is divided into a separate bundle to avoid re-downloading the same code on subsequent navigation.
Following the initial page load, Next.js can begin preloading the code for any subsequent pages that users are likely to visit. In a typical Create React App application, this will require lots of codes and sometimes additional libraries.
Although Next.js is a great framework, it has a few drawbacks in terms of development.
For example, Next.js has relatively high development complexity on the server side. Some JavaScript plugins and libraries, such as Bootstrap and other libraries that support DOM manipulation, are designed to run only on the client-side requiring extra effort from the developer to handle client- and server-side validations to determine when to import and use such libraries.
Additionally, Next.js requires a Node.js server to run JavaScript on the server side. Thus, you must first set up the server before you can use Next.js to create your app.
If you are considering migrating your existing non-ejected Create React App project to Next.js, the Next.js team has documented a step-by-step guide on migrating to Next.js in the official Next.js documentation.
We opened our experiment with a question: Does SSR improve application performance?
To investigate this question, we built two nearly identical applications, one that uses client-side rendering with Create React App and one that uses server-side rendering with Next.js.
The Lighthouse results from our simulations showed better metrics in the Next.js application in all significant categories, especially First Meaningful Paint (87.69 percent decrease), First Contentful Paint (87.69 percent decrease) and Time to Interactive (27.69 percent decrease).
The results speak for themselves. What has your experience been like with Next.js vs. Create React App?
Install LogRocket via npm or script tag. LogRocket.init() must be called client-side, not
server-side
$ npm i --save logrocket
// Code:
import LogRocket from 'logrocket';
LogRocket.init('app/id');
// Add to your HTML:
<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>

Build a CRUD REST API with Node.js, Express, and PostgreSQL, then modernize it with ES modules, async/await, built-in Express middleware, and safer config handling.

Discover what’s new in The Replay, LogRocket’s newsletter for dev and engineering leaders, in the March 25th issue.

Discover a practical framework for redesigning your senior developer hiring process to screen for real diagnostic skill.

I tested the Speculation Rules API in a real project to see if it actually improves navigation speed. Here’s what worked, what didn’t, and where it’s worth using.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up now