How Next.js Works Under the Hood, Server vs Client components
Hello, I wanted to do a deep dive into Next.js's structure and how it differs from traditional HTTP servers such as Flask, Express.js, and many other frameworks.
As you know, Next.js is specifically written for React applications, it is made to develop full stack applications faster and easier with React as the frontend framework. So it has many internal logics specifically for react components.
If you used Express or Flask before, some of Next.js's behavior might feel like pure magic
Let's get started one by one, shall we.
(Due to license concerns I'm resisting the urge to put anime gifs here)
Server and Client Components
There are two types of components in Next.js, not just the normal components you make in React. They are pretty self-explanatory.
Client Components are components that developers opt into by putting "use client" at the top of the file. Most of the internal logic can be separated from the server and handled by the client, which in this case is the user's browser.
When components are marked as Client Components, it allows them to be hydrated. This is due to the architecture of how Next.js handles Client Components.
At the first initial request, Next.js will send a markup of the components, static HTML and classes. Which allows the browser to render the view and UI quickly.
It doesn't really contain any functional JavaScript that does any handling, just a quick view. This allows the user to always see the UI even before blinking their eye.
It will then also send out the JS Bundle to the client, which contains the JavaScript functions of the Client Component. React then maps those functions to their corresponding DOM nodes by matching them through their position in the component tree. Basically like a tree structure of all the HTML elements, you know the one you see when you open up the div tool in DevTools.
In Express or Flask, none of this exists. It is usually a one and done deal where as a simple HTTP server it is just there to serve the content, and unless you explicitly build it to be more efficient on sending back the data it will just serve all the static and sources you need all at once. No separation of server side and client side, just one response and that's it.
Server Components
Server Components are components that require server-side rendering and logic. The Next.js team does something a bit different to handle rendering to make it more performant.
Rather than waiting for the full component to render before sending anything back, it uses Transfer-Encoding: chunked to send data in chunks to the client as it renders. The client will also show a loading view while the contents are being rendered and fetched from the server.
The chunked data that is sent to the client is usually the HTML fragments so that the browser can start rendering, and inside the script tag it uses the RSC payload for React to hydrate it later.
RSC Payload (React Flight)
For Server and Client Components to communicate, Next.js uses the RSC Payload, also called React Flight.
Example:
0:["$","div",null,{"className":"layout","children":["$","header",null,{"children":"My App"}], "$L1"}]
1:I["app/SearchBox.tsx",["chunks/SearchBox.js"],"SearchBox"]It is a bit weird looking at first but it is pretty straightforward once you break it down:
- Line 0 is the standard HTML elements rendered by the Server Components
$L1is a pointer telling React to look at reference 1- Line 1 is that reference, it tells React that SearchBox is a Client Component and where to find it in
chunks/SearchBox.js
I didn't plan what to write after that selection, so I'm going to write everything else I have in my mind that just notes on stuff I learn
Virtual DOM
DOM: Document Object Model
it is a tree like structure of the html elements that are rendered in front of you when you arrive at a webpage.
Due to how expensive manipulating the DOM in browser, react took a different route.
Remember compiled vs interrupted languages, manipulating the DOM on the fly is expensive. So by determining what needs to be change, we can optimize the steps it takes to manipulate the DOM to arrive in our end results.
So in react, they store a virtual DOM as objects internally, and use it to compare when a DOM manipulate action happens. Using this we can optimized manipulate DOM to arrive on our desired state.
More on why it is expensive manipulating the DOM: https://medium.com/@sidhemu09/dom-manipulation-an-expensive-narrative-490f0e1407ae