-
Static rendering means that routes are prerendered at build time. The result is cached and can be reused across requests. Static routes are fully cached in the
Full Route Cache. - Dynamic rendering means that routes are rendered server-side at request time. The result is served to users but not cached.
These are the definition, the theory. In this chapter I will try to explain these concepts in a more grounded, practical way. Instead of sticking to theory, I'm going to start with simple examples and build from there. I'm hoping that this will help you gain a better understanding.
A simple example
Note: the examples are available on github.
Let's create a route and three components:
// app/static-example-route/page.tsx
export default function Page() {
return (
<>
<Header />
<Main />
<Footer />
</>
);
}
export default function Header() {
return <header>***** Header *****</header>;
}
export default function Main() {
return <main>***** Main *****</main>;
}
export default function Footer() {
return <footer>***** Footer *****</footer>;
}
This page would then look like this:
***** Header *****
***** Main *****
***** Footer *****
Deployment
In development, we run a dev server (next dev), but that is of no use here. Static and dynamic rendering are only relevant in production. We can do this locally too by first running next build and then next start. Note that when you deploy to an actual live server like Vercel, this server will also run these same commands.
-
next build: your app gets compiled into packages (this is the very short version; a lot happens here). Anything that happens here, happens atbuild time. -
next start: the prepared packages start running on the server (a local server in our case). The server is now ready to receive requests: e.g. a user visiting a URL or an app making an API call. When the server does something after receiving a request, the server runs atrequest time. Anything that happens on the server (like rendering or data fetching) isserver-side.
In the build step, Next looks at each route and decides if it should be rendered static or dynamic.
Rendering
But what is the rendering portion in the term "static" and "dynamic" rendering?
In our example, we have React components in TypeScript. Browsers can't read these directly. They need HTML, JS and CSS. Rendering means turning React components into HTML pages.
The difference between static and dynamic rendering is when the rendering happens. Static rendering happens at build time (when running next build), dynamic rendering happens at request time (after next start, when the server receives a request).
Dynamic example
To illustrate why we need dynamic and static rendering, we use a new example. We make a new route:
// app/dynamic-example-route/page.tsx
export default async function Page({
searchParams,
}: PageProps<'/dynamic-example-route'>) {
const resolvedSearchParams = await searchParams;
return (
<>
<div>Hello {resolvedSearchParams.name}</div>
<Header />
<Main />
<Footer />
</>
);
}
We made some small updates to our previous example. We added the searchParam page prop and a new line: <div>Hello {resolvedSearchParams.name}</div>. When visiting the route /dynamic-example-route?name=peter we now expect to see this on our page:
Hello peter
***** Header *****
***** Main *****
***** Footer *****
This is a dynamic route. It is rendered at request time. I cannot be rendered statically because at build time there is no request from a user. There is no search param (?name=peter).
Let's go over this again. At build time, Next takes a look at the route /dynamic-example-route. It checks all the components that make up the route. In the page component (/app/dynamic-example-route/page.tsx), Next encounters the searchParams page prop and decides this route cannot be statically rendered because there are no searchParams. Therefore this route becomes dynamically rendered.
The searchParams prop is not the only thing that forces a route to become dynamic. Here is the full list of APIs that trigger dynamic rendering:
- cookies function
- headers function
- connection function (more later)
- draftMode function
- searchParams prop
- unstable_noStore (legacy)
- dynamic fetches: fetch with { cache: 'no-store' }
Cookies, headers and searchParams prop should be clear by now. We cover connection and dynamic fetches later. First, I want to go back for a bit.
Next build
Let's actually run next build locally now. The route /dynamic-example-route should be rendered dynamically because it uses the searchParams prop. The other route /static-example-route should render statically.
next build
First thing to note is the log in your terminal. Here is screenshot:
We're interested in the routes part. Route /dynamic-example-route has ƒ meaning it's a dynamic route. Route /static-example-route has ○, so static render.
We ran next build. The route /dynamic-example-route has not been rendered. This will only happen at request time when we start up the server and it receives a request. But, the other route - /static-example-route - has been rendered (prerendered). Where is it? In the packages Next just built and placed in the .next folder:
Most of the things in this build folder are unknown to me too, don't worry,it doesn't matter. It's under the hood React and Next stuff. What we are interested in is this subfolder: ./next/server/app. This holds all the routes for the Next app router. (There is also a folder pages for the old pages router).
Here's that app folder:
Notice that these are all files and folders that are named after our routes. By default we have routes
- home
- global-error
- not-found
And we added the routes:
static-example-routedynamic-example-route
Inside our ./next/server/app build folder: we are interested in the .html and .rsc files, ignore the folders. We can see static-example-route.html and static-example-route.rsc. These are our prerendered static files for route static-example-route.
The dynamic route dynamic-example-route is missing. There is no dynamic-example-route.html and no dynamic-example-route.rsc. This is as expected. Dynamic routes are not rendered at build time.
Prerendered files
next build statically rendered our static-example-route route. What did it render? An HTML file and an rsc payload file. Why?
The html file gets served on initial load. The initial load is when a user enters your app (e.g. when redirected by Google or by directly entering an URL in the address bar and hitting enter). On initial load, Next serves the prerendered html.
After the initial load, a user can navigate inside your app. We call this client-side routing. When going from one route to another inside our app, Next serves the .rsc file. This makes sense, Next is after all a Single Page App. No full page reloads on internal navigation.
Prerendered HTML
This is what the html file looks like:
It is fully optimized html and js, ready to be viewed inside the browser. It has links to js files, font preloading, meta and a title tags, favicon links, ...
But, when we remove all of the scripts and meta tags and run prettier, we can see what interests us:
Look familiar? That is exactly how our static-example-route should look like in html.
Prerendered .rsc
The rsc file: rsc stands for react server component payload: it's "a special data format, optimized for streaming". It's a bit like the DOM, where all the elements and nodes are represented inside a nested object. Here's the rsc for our static route:
Don't worry about this, you are not meant to read or understand this. I highlighted the text content from our components. Only thing to remember: our components were also prerendered into an .rsc file.
Earlier, we described rendering as turning React components into HTML pages. Having just looked at this rsc payload, we must expand that definition. Rendering is turning React components into HTML pages or into react server component payload. That is a JavaScript object that describes an HTML element.
A big caveat
All the components in our examples thus far have been server components. All the prerendering we looked into is how server components are prerendered. Client components work differently. We will explore this in the next chapter.
Recap
We ran next build. Next evaluated all the routes in our project. It came across the static-example-route route and rendered the route statically. Static because there are no dynamic elements. A static render means files were created including an html file for initial load and an rsc for client side routing. The dynamic route we created - dynamic-example-route - was skipped because it contained a dynamic element: searchParams.
We are now ready to go back to the definitions from the beginning of this chapter:
- Static rendering means that routes are prerendered at build time. [...]
- Dynamic rendering means that routes are rendered server-side at request time. [...]
Static and dynamic rendering happens at route level, not component level and it is Next that decides. A route will be dynamically rendered (server-side at request time) when Next encounters a dynamic element inside a component. Dynamic elements are:
- cookies function
- headers function
- connection function
- draftMode function
- searchParams prop
- unstable_noStore (legacy)
- dynamic fetches: fetch with { cache: 'no-store' }
In any other case, the rendering will be static. That is, it is prerendered at build time. We looked at some of these prerendered files.
Dynamic rendering
Dynamic routes are not rendered at build time. They are rendered at request time. Only at request time do we have access to things like headers, cookies, searchParams or a (fresh) dynamic fetch.
What does dynamic rendering do? Exactly the same as static rendering. That is, create a lot of files, including an html file and an rsc file. It just does it at request time, using a fresh request. I can't show you these files since they are rendered server-side and are not saved.
Let's quickly look into connection and dynamic fetches, 2 of the dynamic elements the cause routes to be rendered dynamically.
Dynamic fetches
Using the fetch api in Next is a bit tricky:
Next.js extends the Web fetch() API to allow each request on the server to set its own persistent caching and revalidation semantics.
This caching of fetch() is actually a second form of caching Next provides called data cache. But that's for a later chapter. Right now, we are concerned with dynamic fetches. These are fetches that have the setting { cache: 'no-store' } as their second parameter:
let data = await fetch('https://api.vercel.app/blog', { cache: 'no-store' });
{ cache: 'no-store' } means no cache. We opt out. We don't want cached data. We want to go to this URL now and retrieve the latest data. Not some old, stale data.
For example: in a weather forecast app you would like the weather right now. Not the weather from a day or week ago. In an erp app you want to know the current state of your inventory. Is product X in my inventory right now because I need it. Stale data like "you had it last week" is quite useless.
Back to dynamic rendering. If Next encounters a dynamic fetch in a route, it will render the route dynamically. There will be no Full Route Cache for this route. Since we explicitly set cache to 'no-store', the fetch data won't be cached either. More on this later.
So, dynamic fetch -> dynamic render.
Connection
Next decides when to render statically or dynamically (headers, cookies, searchParams, dynamic fetch,...). You cannot force a route to be statically rendered expect by not using dynamic elements.
But, you can force a route to be dynamically rendered, by using connection(). Next gives 2 examples: Math.random() or new Date().
Let's say that for some reason, you need a random number in a component. But the route in the component has no dynamic elements. What happens, your random number becomes the same for everyone because the route will be statically rendered. To avoid this, use connection(). Using connection() will force the route to become dynamic.
The caching part
Back to the definitions:
-
Static rendering means that routes are prerendered at build time. The result is cached and can be reused across requests. Static routes are fully cached in the
Full Route Cache. - Dynamic rendering means that routes are rendered server-side at request time. The result is served to users but not cached.
Static routes are fully cached. Dynamic routes are not cached. The cache for static routes is called Full Route Cache and it is one of the four caches that Next provides.
Note, caching is a mechanism. The prerendered content inside the .next/server/app folder (that we looked at earlier) is not the cache. It is prerendered content and this is what the caching mechanism serves. Every user will be served the same content.
Dynamic routes are not cached. The process of compiling routes into html or rsc happens server-side at request time. The result of this compiling is served to the user but not cached. This means that each user gets their very own server-side render.
Concluding
Static and dynamic rendering should now be concrete. It should also be clear how the process of static rendering leads to the Full Route Cache. Since there are no dynamic elements, every user gets served the same content. We prerender this content in advance during build time.
This is only part of the story. We left out client components. These behave differently during static and dynamic rendering. This is for our next chapter.
If you want to support my writing, you can donate with paypal.






Top comments (0)