Compare and Contrast: CSR, SSR, and SSG in NextJS

Rick Moore
Nerd For Tech
Published in
7 min readJul 24, 2021
Photo by Christopher Robin Ebbinghaus on Unsplash

Since the release of NextJS v9.3, we’ve been given three different rendering options for our React apps.

  • Client-Side Rendering
  • Server-Side Rendering
  • Static-Site Generation

Each of these methods has its use cases and benefits. Let’s explore the situations where we’d want to choose one of these over another and some examples of how to implement each in our code.

1) Client-Side Rendering

Photo by Remotar Jobs on Unsplash

How does it work?

Client-side rendering essentially means that the majority of the work (fetching data and creating HTML) is done by the browser, on demand, as the user needs it.

a) The client requests a page through a URL.

b) The server responds to the client with a blank page, with references to JavaScript files.

c) These are downloaded and run by the browser, and used to build the page.

d) Data is fetched and filled in from an API based on the needs of the JavaScript.

This has some benefits, but presents some issues as well:

Client-Side Rendering Pros:

  • It’s fast! It’s easy for the server to vend a blank page, and this method only generates the required HTML to be displayed, so your browser can easily handle a chunk of elements with hide and show events. Although there is more code to handle, it doesn’t take much time to render. Due to lazy loading, client-side rendering can be much faster than server-side rendering.
  • It’s performant! Unlike traditional HTML pages which need to refresh or re-render an entire page, client-side rendering simulates different pages, but loads them on a single page. This puts less pressure on memory and processing power, which gives you quicker results than server-side rendering.
  • Great for single-page-applications. No other model supports SPA’s, and this allows us to reuse UI components across our application, without requesting them again from the server. This ties in to the fast and performant nature of CSR.

Client-Side Rendering Cons:

  • You are initially serving a blank page, so the client is also going to see just that when they visit your page for the first time: a blank page. During this initial load of your JavaScript application and the building of the page, the wait is longer than you would get with server-side rendering.
  • Search Engine Optimization or SEO. Since your meta data is being loaded by the JavaScript on the first pass loading, search engines will not see that data when their webcrawlers hit your page. This isn’t a problem for server-side rendering, as the initial page that is served to the client has all of this data already.
  • JavaScript bundles can be quite large these days, so particularly on slow connections, downloading all of this code to the client can take some time, again making the initial loading of the page slower than ideal.

Implementation

How do we tell NextJS that we’d like to use CSR? Let’s take a look at some code.

We don’t need to use any special NextJS functions in our React components for CSR. We can make traditional HTTP requests either using an input or a hook like useEffect.

import React, { useState, useEffect } from 'react'

const Home = () => {
const [data, setData] = useState([])
useEffect(() => {
const getData = async () => {
const response = await fetch('https://official-joke-api.appspot.com/random_ten')
const data = await response.json()
setData(data)
}
getData()
})
return (
<main>
<h1>Here are some Jokes!</h1>
<ul>
{data.map(joke => (
<li key={joke.id}>{joke.setup} - {joke.punchline}</li>
))}
</ul>
</main>
)
}

export default Home

Straight forward setup, the server isn’t doing any of the heavy lifting. The client will receive this code, make the request itself and build the page.

2) Server-Side Rendering

Photo by Taylor Vick on Unsplash

How does it work?

Server-side rendering allows us to run our React application on the server, so our heavy lifting is finished before the site reaches the browser.

a) The client requests a page through a URL.

b) The server receives the request and runs the attached Javascript, building it’s own DOM elements.

c) The server requests the needed data from an API, and feeds that data to the React component as props.

d) The server builds the HTML and sends the completed page in a response to the client.

Server-Side Rendering Pros:

  • It’s immediate! When the client receives its first page, the data is already available. This is ideal for slower connections.
  • No client fetches! The data has already been retrieved and implemented by the server, so there is no need for the client to make any HTTP requests itself.
  • Great for SEO! Because all the meta data has already been rendered into HTML when the page is requested, webcrawlers will see all the data from the application, which is ideal for SEO visibility.

Server-Side Rendering Cons:

  • Slow page transitions. You’re basically rendering your app twice, once on the server and once on the client, so especially if your app is quite large, this can affect loading times between pages.
  • Latency. Since the server is doing the rendering work, if there are many users accessing the application at the same time, they could experience latency in the loading of the application.
  • UI compatibility. Some UI components may depend heavily on the window object, which essentially doesn’t exist when the page is being rendered by the server. This can cause some compatibility issues with certain libraries.

Implementation

Let’s take a look at how to create a component that is server-side rendered.

import React from 'react'export async function getServerSideProps(context) {
const response = await fetch('https://official-joke-api.appspot.com/random_ten')
const data = await response.json()
return {
props: { data }
}
}
const Home = ({ data }) => {
return (
<main>
<h1>Here are some Jokes!</h1>
<ul>
{data.map(joke => (
<li key={joke.id}>{joke.setup} - {joke.punchline}</li>
))}
</ul>
</main>
)
}

export default Home

Exporting this NextJS function getServerSideProps will instruct the application to retrieve its data before leaving the server, and feed that to the component as props. The HTML is then rendered and shipped to the client.

2) Static-Site Generation

Photo by Zach Vessels on Unsplash

How does it work?

Static-site generation creates a number of static paths based on the data needed for the page. At build time, these many paths are rendered out into static pages, and served incredibly quickly to the client.

a) The client makes a request to the server.

b) The server receives the request, and has already built the needed HTML pages for the static data.

c) the server responds with completed, pre-built pages based on the URL requested.

Static-Site Generation Pros:

  • Just like server-side rendering, static-site generation is immediately available and doesn’t require and additional data fetches from the API.
  • Great for SEO! Since the HTML is built before being sent to the client, the SEO visibility is ideal with SSG.
  • Incredibly fast! Serving time for static pages is incredibly fast, comparable to the vending time of the blank page we send in client-side rendering.
  • Serverless! Serving static pages doesn’t require a server to monitor, so you can take full advantage of services designed to serve static pages.

Static-Site Generation Cons:

  • Build times can be quite long if your site is large and complicated.
  • Data is only fetched once at build time, so you can’t refetch and refresh data on the fly.
  • You may run in to the same UI compatibility issues we had in server-side generation, as the window object wouldn’t be available at build time.

Implementation

The NextJS docs describe two scenarios for static-site generation. The first is creating a static page that doesn’t depend on any outside data. In this case we only need to use the NextJS getStaticProps function to load some static assets.

function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>{post.title}</li>
))}
</ul>
)
}

export async function getStaticProps() {
const res = await fetch('https://.../posts')
const posts = await res.json()

return {
props: {
posts,
},
}
}

export default Blog

Just like getServerSideProps, we feed the data in to our components as props at build time, and the page gets generated into a static page to be served on request to the client.

If we need to create dynamic routes for our data, for example, to go to a blog posts page to view more information. To handle this, Next.js lets you export an async function called getStaticPaths from a dynamic page using their syntax (pages/*resource*/[*paramater*]): pages/posts/[id].js in this case.

This allows you to pick and choose which paths you would like to pre-render into static pages at build time.

export async function getStaticPaths() {
const res = await fetch('https://.../posts')
const posts = await res.json()
const paths = posts.map((post) => ({
params: { id: post.id },
}))
return { paths, fallback: false }
}

We can also set a fallback, which is false in this case, so if the path is not found, we’ll just render a 404.

Conclusion (use cases):

  • Client-side rendering is great for logged-in experiences and single-page applications. If you need to authenticate a user on every interaction, CSR is the way to go. Creating proof of concepts where performance isn’t crucial is another use case.
  • Server-side rendering is ideal for creating pages for slow connections, and keeping SEO visibility strong.
  • Static-site generation would be perfect if I needed the benefits of server-side rendering but also only depended on a dataset that didn’t often change, as the page would need to be rebuilt to reflect new dependent data.

NextJS is on the cutting edge of maximizing the experiences on the internet, and these options give a wide flexibility in our site generation schemes. Take a look at the docs to learn more!

--

--

Rick Moore
Nerd For Tech

Software Engineer at Thrive TRM. Specializing in Ruby on Rails, React/Redux and JavaScript.