Next.js SSR vs. SSG & Incrementalism

Part 2

Nate Jones
JavaScript in Plain English

--

Photo by Scott Graham on Unsplash

In my last post I reviewed some of the basics on Client Side Rendering (CSR) vs. Server Side Rendering (SSR). Check that out here if you’re unfamiliar:

Create React App was the example of a CSR app, and Next.js was the lead-in to the world of SSR. As noted, Next.js takes all of this rendering on the server to the next level with lots of options, so this post will explain some of its power with use cases.

🗒 quick note: you can google “CRA SSR” and there’s plenty to do. I’m just talking about the basic out of the box functionality.

Pre-rendering in Next.js

Next.js focuses on pre-rendering, which is a more generic term than SSR. The docs describe that pre-rendering includes 2 things:

  1. Server Side Rendering (SSR), just like the last post talked about
  2. Static Generation (SSG = Static Site Generation)

SSG is similar to SSR, in that the server is responsible for pre-rendering HTML (no CSR!), but it happens at build time, i.e. an npm run build for an out-of-the-box Next.js project. Generally speaking, you would statically build pages that will be the same for all clients (marketing pages, blog post pages, etc. More on use cases coming up).

Why are things generated at build time great?

  1. When you run a build command, this is outside of the workflow of someone actually visiting your site, so no problem if it takes a little time
  2. Once your SSG pages are built, they can sit in a CDN. That means the HTML is sitting at Edge Locations around the world and it’s super fast for a browser to request and render these for the user
  3. Your web server won’t have to do any work for statically generated pages that are being served from a CDN, so you don’t need to worry about its performance in this context

These benefits blend together benefits from CSR and SSR like I described in my last post — namely server does the work to create actual HTML, but we can still put it in CDN. The theme in the Next.js docs really becomes “if you can use SSG for a use case, do it.

The Next.js docs are great, but there were a couple concepts that tripped me up regarding the fancier options for SSG. The use cases below are an attempt to clear some things up.

Code

Code samples can be found in this repo here https://github.com/neightjones/next-ssr-ssg-blog

Use Case 1: Simple Marketing Page

This example is clear-cut. Let’s say we have an ‘About Us’ page made up of a component with no data fetching or anything — just a static page. Check out the AboutUs page in the repo.

We definitely want this statically generated at build time, because this is as simple as it gets. Every user will see the same thing, so it can be served right from a CDN. Luckily, SSG is the default for Next.js. When you run npm run build, this will be turned into a basic HTML page that’s all set to go.

Use Case 2: List of Blog Posts

Now that we’ve moved beyond Case 1, the trickier cases will all involve some kind of dynamic content. That can mean 2 things:

  1. Some content on the page will change over time (e.g., you add a new blog post to your blog, so now the page listing them out needs to show the new one)
  2. The set of pages that exists expands due to new content (e.g., now that you added a new blog post, you’ll likely have an extra page like /posts/<new_post_id>

Let’s focus first on the component to list out all our blog posts. Is this a case to use Server Side Rendering in Next.js? After all, as blog posts are added, and I refresh my browser, I’d like to see all the newest posts. With SSR, my request for the list page will be constructed on the server each time, which will then always get the newest content from the database (or file system… however my blog posts are stored). SSR will give you the results you’d like, but Next.js can do better.

Enter Static Generation with Data. Next.js has a built-in way to do SSG that relies on external data at build time, so you can pre-build everything that’s needed and put it in the CDN. All you need to do is implement an async function called getStaticProps on one of your page components.

I won’t reinvent the wheel, but here’s a simple example. Check out the docs here for a more detailed description. Link to repo component here.

Since getStaticProps is implemented, Next will know to run the getBlogs at build time (and on the server), which will create the final HTML that shows the list of blog posts. This page can sit in the CDN so it’ll be super fast to access and render in the browser.

We’ll probably want one page per blog post, too, like /posts/<some_post_id>. Next.js also makes it easy to generate these paths & pages statically at build time. So, if you have 3 blog posts with ids 1, 2, and 3, you can use SSG to pre-render 3 appropriate blog pages, with the paths all set to go (/posts/1, /posts/2 and /posts/3). To do this, you implement getStaticProps in conjunction with getStaticPaths. No need to recreate the docs, so check that out here.

Thanks to Static Generation with data, we can now build out all of our blog post pages and routes at build time, which is great. What happens you write a new blog post? Everything was generated already at build time only, so you won’t see the new post if you refresh your browser. In this case, the best thing to do is rebuild the site, so the newest content becomes available. This might seem slightly annoying, but the content doesn’t change too frequently, and you can automate your build / deploy process for your blog.

The need to rebuild and deploy certainly begs the question, though — at what point is the content changing too quickly or growing too large in size to justify rebuilding your site all the time? Another factor to consider is which users can access various content? For the blog example, I assume it’s all totally public. But if there is some private, per-user data, we won’t want to pre-render everything with user data baked in. The next use case shows off a couple more SSG tricks in Next.js to handle more complexity.

Use Case 3: Growing E-commerce Site

This example is based off of this blog post from Vercel (the people who make Next.js). I’ll summarize but I recommend going through their full post as well.

We handled the pre-rendering strategy for your blog site with Static Generation with Data, above. Now, you have a rapidly growing E-commerce site. Thousands of products will be added this week, and the existing product listings will change somewhat frequently (pricing, inventory, etc.).

Let’s focus first on the page that lists the products (e.g. /products), as well as the individual product pages (e.g. /products/1). What happens if we try to apply the Static Generation with data technique?

  1. We’ll hit an issue as products are added to the site. We would have to rebuild the site very frequently, and it would take a long time because we’d be statically generating thousands of product pages. This is infeasible.
  2. Similarly, we’ll have an issue as products change. Again, we would have to rebuild the site to reflect a product change to customers. Also infeasible.

We want to make sure customers see the newest data so they can buy products. Should we finally use SSR to ensure data is up to date on each request??

As it turns out, probably not! Next.js has a concept called Incremental Static Generation that helps dynamic sites like our E-commerce store stay in the world of Static Generation (Next.js does everything it can to help us use SSG!). As I’ve been working more and more with Next.js, I’ve found this to be some of the coolest stuff.

Case 1: Add Product Pages Over the Life of a Statically Generated Build

Let’s say we had statically generated our E-commerce site with 100 products. But now 1,000 new products will be listed this week. We can use a key called fallback inside of our call to getStaticPaths (I linked this above, too, but this is the case where you’re using SSG and automatically generating all paths and product pages at build time).

Normally, fallback will be false. This does what you’d expect — any path that hadn’t been generated at build time will return a 404. This is good because if someone goes to /products/<non-existent product_id>, a 404 is correct. However, you can unlock some special behavior if fallback is set to true. Check out the official fallback: true docs.

With true, a 404 won’t be returned for an unknown path. Instead, you’ll get indication from Next that you should display a “Fallback” page (essentially a loading page). Meanwhile, the server will lazily generate the appropriate page for the user. Here’s the workflow:

  1. I add a product to the site with a new id of 5000
  2. A user tries to go to the page /products/5000, but it doesn’t exist quite yet (of course not, because I just added the product)
  3. The browser will display a Fallback / Loading page for the user (no error or anything like that)
  4. The server generates the HTML page for product 5000, and then adds 5000 to its set of known paths (now product 5000’s page can be cached in CDN)
  5. The user will now see the product page instead of the Fallback
  6. For all future requests for this product during the lifetime of this deployment, product 5000 is all set to go — the work just needed to be done once, lazily!

Case 2: Update Product Pages Over the Life of a Statically Generated Build

This case is even easier in Next.js. This one is called Incremental Static Regeneration, since this is all about re-generating a page periodically after the initial build.

All you need to do is add a key called revalidate in the call to getStaticProps. For example, revalidate: 60 (60 seconds) will have the following workflow:

  1. Initially, a page for product X is built. If it was lazily built, the user will be seeing this fresh product page at time 0
  2. Let’s say 1 second later, a change was made to the product’s price
  3. Now, about 30 seconds later, another user goes to the page for product X. They’re still going to see the old pricing information though, because regeneration only happens every 60 seconds
  4. After a full minute goes by, another user requests the page and Next knows to regenerate the product X page, which will pull in new pricing (though for this user, they might still get the old page once more, but they’ve tipped off Next to generate the new one)

Now our E-commerce site is statically generated, and yet we have dynamic elements including adding new products and updating products! That’s great news — Next gives us the best of both worlds in these scenarios.

Edit: I had mentioned in this series how one big benefit of static generation is that your web server is out of the picture, so you don’t need to worry about it getting overloaded. Note that for this incremental static generation case, while the CDN layer is critical in serving the content once generated, the web server is around in this case to create product pages lazily, as well as regenerate pages every so often.

A Note on the “Shopping Cart” for the E-commerce site

Note that everything discussed above dealt only with public information that everyone could see. But a shopping cart is only relevant for a specific user. So at best you can render some parts of the page, but ultimately, the data regarding what the user has in her cart needs to be found at request time. You can use SSR if you want here, but the same Vercel post recommends Static Generation without Data + Client-side Fetching.

This means that Next will statically generate the generic parts of the shopping cart page (layout, styling, etc.), which can be cached in a CDN. Then, it’ll be up to the browser to request cart details from the server at request time. This is preferred to SSR because of the performance — if the user navigates to their cart, they’ll very quickly see some content, with loading UI for the actual cart details. With SSR, nothing will be on the page until the entire cart is constructed. Importantly, in this case, it’s all private user information, so we can disregard any SEO optimization and instead optimize for the user seeing something very quickly.

In Next.js, you can choose which pre-rendering and and data fetching strategies you want for each page, so you can have a hybrid approach. Check out Vercel too. They’re the creators of Next.js and they make it really easy to deploy a Next app.

--

--