Pre-rendering with Next.JS

Muge Evren Bulut
Level Up Coding
Published in
5 min readJun 6, 2023

--

Static Generation & Server-side Rendering in details

Web applications usually work with Client-Side Rendering, this means a very small part of the application like some styles, and texts are bundled in minimal HTML files, and when a user starts using the application, contents are rendered based on the user’s actions by Javascript. So most of the rendering work is dependent on user behavior.

But imagine a blog or documentation application, for those the biggest part of the application is content which usually doesn’t change based on the user’s behavior, so in those applications, if you are rendering the content when the user clicks to open a page, this means you have some room for improvements for your application to behave in a more performant way. This is where Pre-Rendering takes the stage. With pre-rendering, initial content is generated on the server, and the browser downloads the bundled files with HTML content already in place without dependency to javascript loads.

Pre-rendering will help you optimize your application with improved search engine optimization, better performance and user experience.

There are some great libraries and tools that help creating such applications. Almost every Javascript libraries like Vue, Reach, Angular have some solution for pre-rendering. In addition there are more specific technologies you can use create even more performant solutions based on your needs like Next.js, Jekyll, Hugo and many more…

I will try to explain concept of Pre-Rendering with Next.js in this article.

What is Next.js?

Next.js is a React framework that helps you develop web applications faster by the help of building blocks provides solutions to common application requirements such as routing, data fetching, integrations etc. For more details of NextJS I highly suggest following the training path that NextJS provides.

Pre-rendering is one of the most important concept that Next.js provides.

By default, Next.js pre-renders every page. This means that Next.js generates HTML for each page in advance, instead of having it all done by client-side JavaScript.

Next.js provides two types of pre-rendering based on when the HTML of the page is generated. Static Generation and Server-Side Rendering.

With static generation, there is no dependency to user request and the HTML of the page is being generated during build time.

Static Generation

With server-side rendering generation of the HTML is done on each user request.

Server-side rendering

How do we decide when to use Static Generation or Server-side rendering? We should think about whether we can make a page ready before user’s request or not. If we can then we should go with the static generation. On the other hand, if our page depends on user’s request or contains data that always needs to be up to date. Then we should choose server-side rendering.

However, Next.js has an even better feature, which is that it is not restricting you to choose only one pre-rendering option, you can choose what you’d like to use for each page, by that you can create an app by using static generation for some pages and using server-side rendering for others.

Data Fetching in Next.js

Next.js provides a set of functionalities that allows us to implement pre-rendering. The two of them are getServerSideProps and getStaticProps, that are responsible for static generation and server side rendering as they already give away the hint within their names.

export default function Page(props) {
// Render data...
}
// This gets called only at build time
export async function getStaticProps() {
// Get external data from the file system, API, DB, etc.
const data = ...
// The value of the `props` key will be
// passed to the `Page` at build time
return {
props: ...
}
}
export default function Page({ data }) {
// Render data...
}

// This gets called on every request
export async function getServerSideProps() {
// Fetch data from external API
const res = await fetch(`https://.../data`);
// Parse the JSON
const data = await res.json();

// By returning { props: { data } }, the Blog component
// will receive `posts` as a prop at build time
return { props: { data } };
}

I will try to explain those with a very simple example and dummy code… Assume that we will create an web application named Traveller, which will have a page where we will list all the countries (travellerapp.com/countries). The second page will display the list of countries that the logged in user travelled (travellerapp.com/travels).

Since countries list is not changing very frequently (hopefully not at all, as Atatürk once said “Peace at Home, Peace in the World”), we can choose static generation for the /countries page which will make sure the page is only rendered on build time.

In contrary, /travels page carry personal travels of the user which can be updated frequently and also extremely user dependent, it should be implemented with server side rendering and generated in every user request.

/countries page with Static Generation & /travels page with Server Side Rendering
// Countries Page Component, 
// receiving all countries from getStaticProps
export default function Countries({ all }) {
return (
<h1>All Counties</h1>
<ul>
{allCountries.map((country) => (
<li key={country.id}>{country.name}</li>
))}
</ul>
);
}

export async function getStaticProps() {

// Call the fetch method by passing the REST Countries API link
const response = await fetch(
'https://restcountries.com/v3.1/all');
const data = await response.json();

// Return the result inside props as allCountries
return {
props: { allCountries: data.results },
};
}
// Travels Page Component, 
// receiving travels of the user from getServerSideProps
export default function Travels({ all }) {
const { user } = getLoggedInUserFromState();
return (
<h1>{user.name}'s Travels</h1>
<ul>
{travels.map((travel) => (
<li key={travel.id}>{travel.country_name}</li>
))}
</ul>
);
}

export async function getServerSideProps() {

// Call the fetch method to my backend service
// which we assumed returns data based on logged in user's session
const response = await fetch('https://mybackendservice.com/getUsersTravels');
const data = await response.json();

// Return the result inside props as travels
return {
props: { travels: data.results },
};
}

As a summary, powerful pre-rendering and data fetching options Next.js offers is great to optimize your app. But as always, it is not the solution for all. You need to tailor your approach to your unique application requirements and user experience. It would be non-sense trying to force static site generation in every page, sometimes it is better just to stick to client-side fetching for better performance.

--

--