Search

Suggested keywords:
  • Java
  • Docker
  • Git
  • React
  • NextJs
  • Spring boot
  • Laravel

Next.js: Prerendering and Fetching Strategies

  • Share this:

post-title

Pre-rendering is the process of generating html pages at build time or on the server. In Next.js, components can be rendered in two aspects: where they are rendered and when they are rendered.

In terms of timing, they can be rendered on the server side or the client side, as covered in a previous article. Now, focusing on server-side rendering, it can be further categorised into two parts: Static rendering and Dynamic rendering

screenshot

 

Static Rendering

In Static rendering pages are rendered/data fetched on server at build time, its result is cached at server and the cached data  is returned every time a request is made. In Next.js Static Rendering is the default rendering strategy. If data changes cache can be revalidated..

For example, Imagine showing the current time on a webpage. With static rendering in Next.js, each time someone looks at the page, they see the time when the page was created, and it doesn't change on refresh. It's like taking a snapshot of the time when the page was made and showing that snapshot every time someone visits.

export default function Home() {

  console.log("Server Component (page.jsx)");

  return (

    <main className="bg-blue-300 p-5">

      Server Component (page.jsx)

      <p> {new Date().toLocaleTimeString()}</p>

    </main>

  );

}

Now to build type npm run build in terminal  and then npm start to start production server

This page is static and shows the same time it was built every time even we refresh the page.

Static rendering is equivalent to Static Site Generation (SSG) in Next 12 and before. Before Next 13, getStaticProps() and getStaticPath() functions are used to implement Static Site Generation.

 

Dynamic Rendering

In Dynamic rendering pages are rendered / data fetched on the server at request time, everytime a request is made and the result is sent to the client without caching the result in the server.It is equivalent to Server Side Rendering (SSR)

Before Next 13, getServerSideProps() function is used to implement Server Side Rendering.

There are 2 ways we can opt component intro dynamic rendering

Route config options

The Route Segment options enable you to customise the functionality of a Page, Layout, or Route Handler by directly exporting variables

Pages can be turned dynamic by exporting a dynamic variable with value force-dynamic , it opts pages from caching, equivalent to Server Side Rendering  in page router.

export const dynamic ='force-dynamic'

 

Dynamic Functions

Dynamic functions depend on information that is only available at the time of a request, There are 3 dynamic function in Next js  Cookies & Header from next server and searchParams (query strings in url)

This information can be only known at request time, pages which are using cookies, header and searchParams need to be run dynamically at request time and cannot be known at build time. If these function are used page are opted to rendered dynamically.

We can also make our page dynamic using  unstable_noStore, it is a  Next.js API used to render a page dynamically. Calling this api prevents the rendered data from caching.

For example, Consider displaying the current time on a webpage. With dynamic rendering in Next.js, every time someone opens the page, it fetches the current time and shows it. So, each request results in showing the latest time, making the content dynamic and always up-to-date.

export const dynamic ='force-dynamic'

export default function Home() {

  console.log("Server Component (page.jsx)");

  return (

    <main className="bg-blue-300 p-5">

      Server Component (page.jsx)

      <p> {new Date().toLocaleTimeString()}</p>

    </main>

  );

}

Now to build type npm run build in terminal  and then npm start to start production server.

screenshot

screenshot

screenshot

The time here changes on refreshing the page, as on each request the page is rendered without caching the results.

Runtime Environments

Next.js offers two runtime environments:

  1. Node.js
  2. Edge

The default Node.js environment provides access to all Node APIs and npm packages. On the other hand, the Edge runtime is based on web APIs and offers a more streamlined access to a subset of Node APIs, which is basically standard web APIs compared to node.js API (we can’t read/write from file system).

Both runtime supports streaming, by default all the pages, routes and segments use node.js runtime.

To leverage these environments, we need to export the route configuration segment, variable named 'runtime’

export const runtime=’edge’’;  // 'nodejs' (default) | 'edge'.

 

screenshot

The Edge Runtime is great for quickly delivering personalised content with small, simple functions. It's speedy because it uses resources efficiently, but this might be limiting in some cases. Also Edge Runtime is limited in its capabilities compared to the Node.js runtime and may not be suitable for all applications.

 

Fetching  

Fetching In Next.13 app router is built on react server components and Web Fetch API, It is recommended to fetch data in server components whenever possible because-

Security: Private tokens and variables can be accessed while fetching, without exposing to the client.

Low Latency: Direct access to the database, fetching and rendering in the same environment reduces communication between client and server.

While fetching  we don't need to fetch data globally and pass the data as props to the necessary components.Instead we fetch data in components which actually need it, without worrying  about the same data being fetched multiple times. This is all because fetch request are memoized, meaning we call fetch function for same data multiple places while executing it only once

We can fetch data on server using fetch api, for example- here we are fetching random user

async function fetchUser() {

  const res = await fetch("https://random-data-api.com/api/v2/users");

  const data = await res.json();

  return data;

}

const Home = async () => {

const user = await fetchUser();

 const { first_name, last_name, email, gender } = user;

  return (

    <main className="bg-blue-300 p-5">

      <h1>Name: {first_name + " " + last_name} </h1>

      <p>Email: {email}</p>

      <p>Gender: {gender}</p>

    </main>

  );

};

export default Home;

screenshot

 

Static vs Dynamic data

Next.js automatically optimises performance by caching the results of data fetching functions. This means that the function only runs once, even if the page is accessed multiple times. This is especially beneficial for static data that doesn't change frequently, such as blog posts. By caching the data, Next.js can serve it to subsequent users quickly and efficiently, reducing server load and improving user experience.

However, this behaviour can be problematic for dynamic data that changes frequently. For such data, you need to opt out of caching to ensure users always receive the latest information.

Next.js extends the Fetch API and allows you to specify the caching behaviour using the cache option. By passing cache: "no-store" to the fetch function, you instruct Next.js not to cache the result and instead re-run the function on every request.

Here's an example:

async function fetchUser() {

  const res = await fetch("https://random-data-api.com/api/v2/users", {

    cache: "no-store",

  });

  const data = await res.json();

  return data;

}

Therefore now this function fetches every time a request is made.

screenshot

On the other hand, if you want your fetch function to cache the response, pass cache: "force-cache".

NOTE- You can also disable caching for specific routes by setting the dynamic or revalidate option in the route segment config dynamic = 'force-dynamic' This option forces the route to be rendered dynamically at page level, regardless of its content. This effectively disables both the Full Route Cache and the Data Cache.This method is useful if you are using a 3rd party library to fetch data such as axios, where you can't pass the cache option.

Built-in Function (Next 12 and before): getStaticProps() & getServerSideProps()

Before the Next.13 app router, using these function  data was fetched on the server, these functions are exported in a page file, and they return the data which can be received by the page component as prop.

getServerSideProps() - This function is called every time a request is made, and the new data is rendered in the page.

export async function getServerSideProps() {

  const res = await fetch("https://random-data-api.com/api/v2/users");

  const data = await res.json();

  return { props: { data } };

}

const Home = ({ products}) => {

// code

}

getStaticProps() - This function is called at built time and the page is rendered, that means the HTML page is generated even before a request is made.

export async function getStaticProps() {

  const res = await fetch("https://random-data-api.com/api/v2/users");

  const data = await res.json();

  return { props: { data } };

}

const Home = ({ products}) => {

// code

}

 

Conclusion

As a developer, the choice between static and dynamic rendering is seamlessly handled by Next.js, which automatically selects the most suitable rendering strategy for each route based on the features and APIs in use. Your decision-making centre around when to cache or revalidate specific data, and you retain the option to stream components of your UI as needed. Also app router optimises React server components with efficient Web Fetch API for secure, low-latency data fetching, enhancing performance through memoization.

Atif Ali

About author
Transitioning from front-end to full-stack web development, I'm eager to share my insights through articles. My constant drive is to explore new technologies and continually push my boundaries.