Next.js (SSR) vs. Create React App (CSR)

Next.js (SSR) vs. Create React App (CSR)

Exploring the performance differences between server-side-rendering (SSR) and client-side-rendering (CSR) through simple examples.

First, you need to understand the basics of SSR and CSR:

The main difference is that for SSR your server’s response to the browser is the HTML of your page that is ready to be rendered, while for CSR the browser gets a pretty empty document with links to your javascript. That means your browser will start rendering the HTML from your server without having to wait for all the JavaScript to be downloaded and executed. In both cases, React will need to be downloaded and go through the same process of building a virtual dom and attaching events to make the page interactive — but for SSR, the user can start viewing the page while all of that is happening. For the CSR world, you need to wait for all of the above to happen and then have the virtual dom moved to the browser dom for the page to be viewable.

Walmart Labs — The Benefits of Server Side Rendering Over Client Side Rendering

We used the popular Next.js library for the SSR solution and the Create React App (CRA) library for the CSR solution.

The Comparison

We explore the performance characteristics of the scaffold example provided by CRA; consisting of an image and some custom styling. We implement the same example with Next.js.

note: The measurements are taken using Chrome Browser through a simulated Fast 3G network; used to highlight any performance differences.

The first load; with nothing cached:

Create React App (CRA)

Next.js

Observations:

  • The display of non-image content is delayed with CRA (2 seconds) as compared to Next.js (< 1 second); with CRA the non-image content cannot be displayed until all the JavaScript files (largest is 109 KB) are downloaded
  • The display of image content is delayed with CRA (2.8 seconds) as compared to Next.js (2 seconds); with CRA the images cannot be downloaded until all the JavaScript files are first downloaded
  • In both examples, the application becomes interactive once all the JavaScript files are downloaded; CRA (2 seconds) and Next.js (2.8 seconds). The largest Next.js JavaScript file is 173 KB (larger than the 109 KB for CRA)

For the subsequent loads; with everything cached (favicon does not matter):

Create React App (CRA)

Next.js

Observations:

  • The display of non-image content and the application becoming interactive is the same with both examples (< 1 second)
  • The display of image content is delayed with Next.js (1.3 seconds) as compared to CRA (< 1 second); with Next.js the images are not cached

The Critical Issues

For CRA the size of the largest initial JavaScript bundle is important during the first load for both the display of all content and for the application becoming interactive.

For Next.js, during the first load, the size of the largest initial JavaScript bundle is only important for the application becoming interactive; the display of all content is mostly unaffected by it.

With Next.js, during subsequent loads, the images are substantially delayed because they are not cached by design as described in a Next.js issue:

@dbo since we don’t hash static files, we are not possible to do that.
It’s a pretty good idea to move your static content into a CDN or something similar and avoid /static as possible as you can.

We should improve this behavior in the future. But it’s not happening anytime sooner.

Contributor — Set proper Cache-Control max-age for static assets

The Solutions

For both CRA and Next.js the size of the largest initial JavaScript bundle can be equally minimized using bundle splitting.

For CRA, code-splitting is something one has to deliberately implement; albeit it is fairly easy to do (especially if one uses the react-loadable library).

One advantage of Next.js is that by its design, it encourages bundle splitting by automatically splitting based on the pages feature (something one is likely to use).

Conclusion

It is important for both CRA and Next.js that the largest initial JavaScript bundle to be be minimized (we want our applications to be interactive). In both cases this is easy to accomplish using bundle splitting. In the case of Next.js, however, bundle-splitting happens automatically in common situations (easier for beginners to get right).

Next.js, by design, serves up static files to be not cached. To be honest, this is a bit of a deal-breaker for me (have not found a good solution here).

Finally, there is the obvious distinction in that the output of CRA (static files) can be served up through any web server (say a CDN) while the output of Next.js (being server rendered) is served up by a Node.js server (going to be substantially slower than a CDN).

Alarmingly, the proponents of SSR make an interesting observation.

SSR throughput of your server is significantly less than CSR throughput. For react in particular, the throughput impact is extremely large. ReactDOMServer.renderToString is a synchronous CPU bound call, which holds the event loop, which means the server will not be able to process any other request till ReactDOMServer.renderToString completes. Let’s say that it takes you 500ms to SSR your page, that means you can at most do at most 2 requests per second. *BIG CONSIDERATION*

Walmart Labs — The Benefits of Server Side Rendering Over Client Side Rendering

In thinking this through some more, the only way I see around this limitation is to run a lot of Next.js servers through a load-balancer (a pretty complicated dev-ops problem).

Addendum: Got a helpful response on how others are deploying Next.js using server-less solutions, e.g., AWS Lamda, Google Cloud Functions, or Zeit Now. Interestingly enough, Zeit is the company that hosting Next.js on their GitHub account.


Next.js (SSR) vs. Create React App (CSR) was originally published in codeburst on Medium, where people are continuing the conversation by highlighting and responding to this story.

Author: NA

Leave a Reply

Close Menu
%d bloggers like this:
Skip to toolbar