NextJS Client-side, Server-side & Static Rendering

The future of SEO and search ranking algorithms are now in heavy favour of static or server-side rendering. This means building static or server-side rendered apps instantly gain advantages in rankings.

In this article, we are going to discuss building static & server-side rendering apps with NextJS, a server-side rendering React framework.

What is Static Rendering & Server-Side Rendering

Server-Rendering (SSR)

In response to each request, renders the app on the server, and then sends the rendered HTML and Javascript back to the client.

Client-Side Rendering (CSR)

Renders the app in the browser on the client at run time.

Static Rendering (SR)

Usually generating a static HTML page for every URL. Pre-built app, or renders the app at build time (e.g. when you run the npm run build or yarn build command). This is the default approach of NextJS.


In NextJS, there are three main functions that are used for SSR and SR, and a hook that for client-side data fetching.

Built time:

getStaticProps — fetches data at build time getStaticPaths — pre-render dynamic routes at build time

Run time:

getServerSideProps — fetches data on each request SWR — fetches data on the client-side at run time

Server-Side Rendering (SSR)

The HTML is generated on the server on each request for the page — the HTML is 'dynamic' rather than 'static' as it will depend on the data required.

Each time a client requests the page, the server will fetch the data for that page, and generate the page's HTML using the data.

Next.js offers two data fetching methods for SSR — getServerSideProps and getInitialProp

Rendering web

In order to prevent the situation where data fetching can happen on both server and client-side, it is preferred to use getServerSideProps over getInitialProp.


NextJS will render pages using the getServerSideProps function on each request and will use the data returned by the function to populate the components props.

Non build time API calls

You should only use getServerSideProps if you can’t access the data needed for the page at build time. i.e. the request has some piece of information that you need to fetch the required data or to render the page.

Note that getServerSideProps ONLY runs on the Server, and is never downloaded to or run in the Client.

const Page = ({ data }) => <div>{data}</div>;

export const getServerSideProps = async () => {
  const res = await fetch('https://path/to/your/api');
  const data = await res.json();
  return { props: { data } };
export default Page;


  • Rendered page will always be up-to-date
  • Can handle dynamic routes on the fly


  • Not possible to be cached by a CDN
  • Can be slow if there is heavy processing to do since the client has to wait for each page after it makes a request

Static Rendering (SR)

Renders the app at build time (e.g. when you run the npm run build command). Generally involves generating a static HTML page for every URL. This is NextJS’s default approach.


Next.JS pre-renders each page at build time and uses the props returned by getStaticProps to hydrate the page with data.

const App = ({ data }) => <p>{data}</p>;

export const getStaticProps = async () => ({ props: { data: 'Hello world' } });

A few things about getStaticProps:

  • Since it executes at build time, it does not receive data from any request that gets made at run time. This includes things like HTTP headers, query params etc
  • It ONLY runs on the Server, meaning that you can write server code here and it won’t be downloaded to or run on the Client, e.g. API calls, database calls, requests, etc
  • You don’t need to make local API calls, as it only runs on the Server, (e.g. /pages/api). Just write the server code directly inside of getStaticProps.


This function allows you to make a list of dynamic routes that you expect to need later on, and then render them at build time (i.e. when you run npm run build in your terminal window). You define them by returning an object with the key of paths (required).

The returned object also has a key called fallback (required) which dictates this behavior.

If you set the fallback key to false, then all non-defined routes 404 out.

If you set the fallback key to true, then when a User requests a non-defined page, NextJS throws up fallback HTML that you have defined in the same file (you just detect the fallback by using if (router.isFallback)) and in the background Next goes and fully generates the page being requested using the URL that the User entered, and then swaps it out for the fallback HTML.

import { useRouter } from 'next/router';

const Post = ({ post }) => {
  const router = useRouter();

  if (router.isFallback) {
    return <div>Loading...</div>;

  return <div>{...post}</div>;

export async function getStaticPaths() {
  return {
    paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
    fallback: true,

export async function getStaticProps({ params }) {
  // if url was /posts/2, params.id will be 2
  const res = await fetch(`https://path/to/your/api/${params.id}`);
  const post = await res.json();

  return { props: { post } };

export default Post;


  • Consistently very fast and performant


  • Cannot handle dynamic routes if you don’t know all the URL’s ahead of (build) time

Client-Side Rendering (CSR)

Renders the app on the Client in the browser at run time.


  • Nothing that stands out over the other options


  • Slows down as the application grows, and should generally be avoided
SSR, SR, and CSR

NextJS on server-side, will render as much HTML with data as it can at build time with getStaticProps, and then if it needs data later on, it will run getServerSideProps.

If you need to fetch data on the client-side, you can use SWR.

The SWR Hook

Client-side rendering is something that should avoid when possible. But when it is required, you can call the SWR hook, to be called at runtime. It stand for Stale While Revalidate.

Stale While Revalidate (SWR)

This is the name of a HTTP cache invalidation strategy.

Here's an example on how to use SWR.

import useSWR from 'swr';

const Post = () => {
  const { data, error } = useSWR('/api/post/123', fetch);

  if (error) {
    return <div>Error loading post!</div>;

  if (!data) {
    return <div>Loading...</div>;

  return <div>{data}</div>;