Sean Connolly Senior Web Architect intensely focused on fast teams and fast apps @thegoldenshun.

Next.js vs. Create React App: Whose apps are more performant?

5 min read 1436

Next.js vs. Create React App: Whose Apps Are More Performant?

Introduction

What are the performance differences between Next.js and Create React App? Let’s unpack that question with some data, but first, we need to understand what exactly we are comparing here.

What is Next.js?

Next.js is a React framework built by Zeit, and according to nextjs.org:

With Next.js, server rendering React applications has never been easier, no matter where your data is coming from.

Next.js also supports static exporting, but for the purposes of this post, we are focused on that “server rendering” capability mentioned above.

LogRocket Free Trial Banner

What is Create React App?

According to its Getting Started page:

Create React App is an officially supported way to create single-page React applications.

Again, for the purposes of this post, we are paying attention to the term “single-page.”

SSR vs. CSR

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.

The experiment

Let’s start our experiment with a question: Does SSR improve application performance?

Hypothesis

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.

Server-Side Rendering Infographic
Client-Side Rendering Infographic

These diagrams postulate that SSR can deliver HTML to the browser faster than CSR can, so let’s make that our hypothesis: a web application built with SSR is more performant than one built with CSR.

Test parameters

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:

  • Fetch data from an API
  • Render a non-trivial amount of content
  • Carry some JavaScript weight

Mobile matters

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 Audits tab in Chrome DevTools.

Audits Tab Contents In Chrome DevTools

We will use the exact settings displayed above:

  • Mobile device
  • Applied Fast 3G, 4x CPU Slowdown
  • Clear storage

Geography matters

If you live in Northern California and you are on servers living in AWS us-west-1 (N. California) all day, you are not experiencing your application the same way as your 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, Zeit’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).

Distance Between Sydney And Boulder

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

CSR Data Fetching
CSR
SSR Data Fetching
SSR

Two applications, one API

The code for the two apps is available in my GitHub.

Here are the applications:

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:

CSR User Interface
CSR
SSR User Interface
SSR

And the JSX is nearly identical:

// 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>
// 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.

The code differs in how the data is fetched from the API, however:

// 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();
}, []);
// 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 with Next.js in their docs.

So what’s with all these components, and why are you using Moment.js?

Going back to our original test parameters, we are trying to test with an application that at least somewhat resembles one we would ship to production. The ThemeProvider, PrimaryNav, etc. all come from a UI component library called Mineral UI.

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.

Results

Here are the Lighthouse results for a full page load on each application.

Create React App Lighthouse Results
Create React App (CSR) results
Next.js Lighthouse Results
Next.js (SSR) results

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.

  • CRA: 6.5s
  • Next.js: 0.8s

According to Google’s First Meaningful Paint docs:

This audit identifies the time at which the user feels that the primary content of the page is visible.

Lighthouse also helps us visualize these differences:

Create React App First Meaningful Paint
Create React App (CSR)
Next.js First Meaningful Paint
Next.js (SSR)

Do the visuals above look familiar? They should because they mimic the diagrams included in the Hypothesis section, where 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:

  1. Download the files for CRA and Next.js
  2. Open https://googlechrome.github.io/lighthouse/viewer/ in Chrome
  3. Drag the downloaded files into the Lighthouse Viewer in Chrome

Measure Performance in Production Environments

Whether you decide on Next.js, Create React App, or another framework, monitoring performance is key. If you’re interested in understanding performance issues in your production app, try LogRocket. https://logrocket.com/signup/

LogRocket is like a DVR for web apps, recording literally everything that happens on your site. Instead of guessing why problems happen, you can aggregate and report on performance issues to quickly understand the root cause.

LogRocket instruments your app to record requests/responses with headers + bodies along with contextual information about the user to get a full picture of an issue. It also records the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

Make performance a priority – .

Conclusion

We opened our experiment with a question: Does SSR improve application performance?

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).

Plug: , a DVR for web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Sean Connolly Senior Web Architect intensely focused on fast teams and fast apps @thegoldenshun.

10 Replies to “Next.js vs. Create React App: Whose apps are more…”

  1. One thing that I believe needs to be clarified is that Next.js takes advantage of isomorphic/universal rendering. When an app is loaded for the first time, it’s loaded via SSR. Afterwards, CSR kicks in, giving you the best of both worlds.

  2. The linked Walmart Labs article is excellent but it’s important to remember it’s from 2017, and they call out synchronous SSR as a bottleneck with renderToString. This is no longer the case with React 16 and the enhancements that Fiber brought.

  3. Interesting that Gatsby is not included in this. With it’s client side re-hydration it’s pretty solid for building web apps as well as static sites.

    Recently built http://www.growable.io with Gatsby and the performance wins out of the box are crazy 🤯

    Any reason why it wasn’t included?

  4. Will, I did not include Gatsby because I believed it to be a static site generator. Static sites will be more performant, but this post was intended to cover applications that require dynamic data.

  5. Interesting, thank you for sharing! I have experience with CRA but am considering Next.js for a new project. I did leverage SSR with CRA though, so I’d be interested to know how CRA with SSR compares to Next.js. My instinct says since the architecture is similar then the performance should be, but then the SSR for CRA is more home-rolled so likely not as optimized. I realize that would be a more extensive benchmark to build out though.

  6. Matt, I suspect the performance difference would be negligible between CRA w/SSR and Next.js w/SSR. I personally prefer not to home-roll when there is a viable meta framework available like Next.js, but that choice is based more on developer ergonomics rather than performance.

  7. I have a question here.
    I’m already aware with the benefits of Nextjs on CRA. I migrated my website(https://m.truebil.com) from CRA to Nextjs. I got 80% improvement on FMP, FCP which also improve the lead conversion by 15%. This is awesome.
    But this is all for new user. It has negative impact on returning user because CRA works better for returning users. For the returning user drop in lead is 10%.

    CRA works better for returning user because all contents(js) are cached on client side so it get rendered faster whereas in SSR first call goes to server which is time consuming.

    How can I improve the performance of returning user?

Leave a Reply