How Next.js builds your app? — Next.js Internals
Create a new app, hit the yarn dev
command, and voila! You have a React application running with the powers of SSG, SSR, ISR and whatnot. All by the powers of Next.js. Let’s get a little intuition into how this framework works internally in this article.
Let’s take a simple Next.js app, with a default /
route. When the client requests the page, the server sends a index.html
file.
The index.html
contains the static initial render of the React components from index.jsx
. What do I mean by that?
Let’s take a simple component:
Upon the initial render of the component, the state bananas
would have a value of true
, and so the output would be:
The index.html
would contain this initial HTML.
index.html
references to multiple JavaScript files. These files load up React and your App
component and attach the App
component to the initial HTML render. This process in React is called hydration.
After hydration, the app starts behaving like any normal React app with all the available features like user interactivity, client side fetching…
In this example, it then mounts the components and fires the useEffect
hook. So we have a state change and a new render:
For all this, Next.js passes your code through 3 stages:
1. Webpack Compilation
Since React components might have JSX which is not valid JavaScript syntax, the first step is to convert the files into valid JavaScript. It uses Webpack and Babel for this.
For Next.js to support features like SSR, SSG, ISR… there should be a compiled JavaScript bundle of all the components each page Next.js has to render in the server. The client also needs a copy of those components, to execute in the browser.
Next.js create 2 separate webpack compiler instances for the server and the client environment.
The client needs optimized bundles which can be directly sent to the browser. It needs to have the React runtime, the Next.js runtime ( main.js ), the webpack runtime, other libraries, polyfills and the components ( index.js ).
You can view all these generated files in .next/static/chunks
after running the build
command in your Next.js app.
On the other hand, the server already has the runtimes and libraries. There’s no need to bundle it with the compiled components.
Along with the compiled components, the server requires some metadata for generating the initial render. These are collected during the webpack compilation process using plugins and then stored in manifest files in the server.
For example, the client side bundles might be chunked or have a defined name. The server needs a list of these JavaScript files that need to be referenced in the head
of the generated HTML file. This information is collected during the compilation by a Next.js webpack plugin, BuildManifestPlugin
. It stores the information in a build-manifest.json
file.
Finally, Next.js also generates something called File Traces (see vercel/nft). These are basically a list of all the file dependencies of the particular page. It helps to reduce file size when exporting.
The index.nft.json
is the file trace of the index.jsx
component. You can view these files in the .next/
and the .next/server
directory.
This compiling happens when the application initially builds, and the files get stored in .next/
directory.
2. Rendering
Next.js needs to create static HTML files from the compiled components. It might do this while building if the pages are static or on demand in the server in case of a SSR page.
Whenever it does it, it takes the exportedgetStaticProps
and getServerSideProps
from the component, executes them, and then passes the value as props to the component.
It then wraps it around some essential Context Providers which feed the data from the manifests (like the list of JS chunks to be used in next/head
) and the environment into the components.
Finally, we get the HTML using the renderToReadableStream
method of react-dom/server
, which is either stored in a file or sent to the client or both.
3. Hydration
On the client side, the browser fetches the HTML and loads up the initial UI. It then loads all the referenced JavaScript files — runtimes, polyfills, components and external libraries.
Once loaded, the Next.js runtime hydrates the components into the DOM using the hydrateRoot
method of react-dom/client.
And you have a working Next.js app!