Hi, my name is David, and I’m passionate about helping people grow on their development journey.
In this article, we are going to take a look at React: what it was intended to do, and the shortcomings of that initial vision.
Then we’ll look at how the Next JS team tried to solve those issues and produced some new difficulties.
And we will look at how the React team has been working hand in hand with NextJS to solve those shortcomings.
And how Next JS’s latest version embodies React’s vision for the future. Which… seems to be drawing inspiration from the past. More about that later.
And above all, we will be looking at what that means for us developers.
But first: what are the problems in React that Next JS tried to solve?
To answer that question, we need to look at how React works, or at least how it worked originally.
Why does it do so?
When browsing between HTML pages, the navigator redraws the page from scratch. That starts off as a blank canvas that get filled in. The lighter the page, the better the connexion, the faster it all draws. But there is always at least a flicker as the page redraws.
Moving that logic to the browser removes that page flicker.
And honestly, it just makes sense to keep the user interface logic right where the user is. Client-side, on the browser. And that’s what React is all about: building rich user experiences.
But there is a problem. Putting all the logic on the client-side comes with tradeoffs. Let’s take a look at those, and look at why Next JS exists.
What are the issues with React’s client-side approach that Next JS tries to solve?
That takes time.
But waiting hurts the user experience. And it also hurts search engine optimisation (or SEO).
And that is where Next JS comes in. It reads the React application directly on the server. It renders the HTML, directly on the server. And it serves that rendered HTML to the client so that the initial page load is fast. So Next JS provides SSR : server-side rendering.
Next JS provides the best of both worlds. Both SEO optimised pages thanks to Server-Side Rendering, and a fluid user experience thanks to React.
However, this produced a few problems of its own. Let’s take a look at them.
The problems with Next JS
As I mentioned earlier, Next JS renders content in two different places: on the server, and on the client.
That introduced two difficulties.
First, NextJS would send all the data to the browser to allow it to hydrate the page. This added quite a bit of overhead to the network payload.
And the dual rendering environment produced a lot of confusion. Next JS files mixed frontend code, and code for fetching data from a server or a filesystem. I’ve seen questions from people asking why their code crashed when they tried to access the window global object. Because they didn’t understand that their code was not only running in a browser, but also on a server. And there is no browser window on the server.
And React is just a library. To make the most of it you often need to rely on the whole ecosystem. On additional modules. But historically, React has been a client-side library, and many modules rely on client-side code.
So, what is the solution?
How React and Next JS have been working hand in hand.
React 18 brought plenty of new improvements. I’ve gone over a number of them previously already. But these improvements all seemed geared to a very specific use case : rendering React on the server. In particular, via a concept called “Server components”.
And now with the release of Next JS 13 we can see all these improvements come together. Next JS now uses server components by default. It has done away with the “getServersideProps” and “getStaticProps” functions, and uses the fetch API instead. You can define your server-side components as being asynchronous functions. Then you use “await” inside the component, and simply fetch your data.
And the idea is to no longer do all this data fetching in the page (and then drill down the data), but for each component to be responsible for fetching its data. This resembles the way the Remix framework does things.
Now, server components look cool, but they do have a few drawbacks. One in particular is that they don’t do state. There’s no place for useState or useReducer on Server Components. If you need “useState” or “useReducer” you will need to define your component as a client component. For that, you just start your file with a "use client"; mention.
How do we fetch data in the client components? On the server side, you can now simply await an asynchronous function that returns the data in your component.
But the way client components work in React, you can’t use an “await” on the top level of the component. However, React has created a new hook called “use”. This hook is intended to be used client-side like an “await”. You pass in an asynchronous function, one that returns a promise. And the use hook manages the asynchronous behaviour.
What does all this mean?
When I first reviewed Next JS, back in version 10, I said it felt like the team was trying to build a full-stack framework. Well, it looks like the React team have given them a helping hand.
But why have they done so?
Because this allows React to position itself as providing more than good UX. Server-side rendering makes React a complete (full stack) solution that scales.
It might look a lot like what PHP and Ruby on Rails have been doing.
But it does so with three differences:
- NextJS and React 18 can stream HTML code, not just render it server side
- the rendering can be done not just on a centralized server, but in a distributed environment, on the edge
- and it does so with excellent tooling and developer experience