Skip to content

Rendering Strategies

One of the very first things we did with Next was to build a “server timestamp”. We did this by rendering a date in the footer of our layout.js component, which is a Server Component:

If you try and deploy this project, however, something funky happens:

Notice that the timestamp isn't changing, even when I refresh the page?

Here's the deal: When we're in “development mode”, Next always uses a “dynamic” SSR strategy. Each route is rendered on-demand, in response to the request.

In “production mode”, however, things work differently.

Earlier, we talked about several different flavors of SSR. We saw three different strategies:

  1. “On-demand” SSR, where the HTML is generated per-request.
  2. “Static Site Generation”, where the HTML is generated for every route when the app is built.
  3. “Incremental Static Regeneration”, where each route is rendered on-demand for the first request, but the HTML is saved for future requests.

In the past, frameworks had to pick one strategy and use it for every route in the entire app. With Next, however, it uses different strategies depending on the route.

And so, for our “server timestamp” example, everything works fine in development, because it's doing the SSR dynamically, on-demand. But when we deploy the app, it uses the production build. And, for this particular route, Next opts for a “static” rendering strategy. The HTML is generated during the build, and the footer's timestamp is frozen in place.

When we build our app, Next tries to figure out the most optimal strategy for each route. The default behaviour is to use a “static” strategy, since it's more performant. It flips to a “dynamic” strategy if we're doing something that can't be calculated during the build (eg. trying to read the cookies for the request, accessing request headers, etc).

To summarize:

  • In development, Next always uses a “dynamic” on-demand SSR strategy
  • In production, Next will try to pick the optimal strategy on a route-by-route basis.
  • The default strategy is “static”, pre-generating all of the HTML during the build process.
  • Using certain Next APIs will cause Next to switch to a “dynamic” strategy for a particular route. For example, if we try to access the cookies, Next knows it needs to do the SSR dynamically, for every request.
  • Because our “Server timestamp” example doesn't use any of these APIs, Next can't tell that we want it to be done dynamically.

The good news is that we don't have to rely on Next to correctly infer our intention. We can explicitly tell it which strategy to use!

Switching rendering strategies

We can instruct Next to use “on-demand” SSR for this route by exporting a special variable:

// src/app/layout.js
export const dynamic = 'force-dynamic';
function RootLayout({ children }) {
return (
<html lang="en">
<body>
{children}
<footer>
Page rendered on{' '}
{new Date().toLocaleString()}
</footer>
</body>
</html>
);
}

We can export this dynamic variable in any page.js or layout.js file. There are several valid values:

  • auto (default) — Next will try and select the most optimal rendering strategy based on the code.
  • force-dynamic — Next will use “on-demand” server-side rendering.
  • force-static — Next will do the server-side rendering during the build, like a static site generator.
  • error — Similar to force-static, but will throw an error if the code tries to do dynamic things (eg. tries to access cookies).

Note that you won't always need to set this value. In many cases, Next will correctly select the best approach. This configuration option is provided as an escape hatch, in situations like this where Next can't infer that it needs to switch to a dynamic rendering strategy.

You can learn much more about this stuff in the Next docs (opens in new tab).

Dynamic Segments

Earlier, we saw how to use dynamic segments to create “catch-all” routes that will match a wide variety of URLs, capturing the value as a variable and making it available to our components.

The example we used was a social network, where the URL took the format /profile/[profileId].

Screenshot of a social media profile

When we use dynamic segments, Next will automatically switch to an “on-demand” rendering strategy.

This makes sense, when we think about it. With the “pre-compiled” strategy, we need to create HTML files for every possible URL, and we can't do that if part of the URL is dynamic and could be anything!

That said, there are other situations in which the universe of possibilities isn't enormous.

For example, our Screen Saver exercise. We're using a dynamic segment there, but there are only 140 named HTML colors. In theory, we could pre-render all of those HTML files at build-time, for optimal performance!

We can do this with the help of the generateStaticParams function:

import React from 'react';
import ScreenSaver from '../../components/ScreenSaver';
async function ScreenSaverExercise({ params }) {
const { color } = await params;
return (
<main className="screen-saver-wrapper">
<ScreenSaver color={color} />
</main>
);
}
const COLORS = [
"AliceBlue",
"AntiqueWhite",
"Aqua",
// ...
"WhiteSmoke",
"Yellow",
"YellowGreen",
];
export function generateStaticParams() {
return COLORS.map((color) => ({
color,
}));
}
export default ScreenSaverExercise;

Next expects generateStaticParams to return an array containing data about all valid dynamic segments. In this case, the produced array would look like this:

[
{ "color": "AliceBlue" },
{ "color": "AntiqueWhite" },
{ "color": "Aqua" },
{ "color": "Aquamarine" },
// ...and so on, for all 140 options
]

During the build, Next will generate 140 HTML files, each one rendering the ScreenSaverExercise component with a different value for the color param, as specified by this array.

We don't have to set the dynamic constant in this case. By specifying generateStaticParams, we've given Next enough of a hint for it to use static generation.

I took the liberty of creating and deploying this project. You can check it out for yourself here:

You can learn much more about this function in the Next docs (opens in new tab).