Next.js is one of the most important and widely-used React frameworks. Today, it has become a consolidated framework for building amazing web applications.
Many advantages can be listed when using Next.js. Performance, SEO, rich developer experience, TypeScript support, smart bundling, and route pre-fetching are just a few examples.
Besides all of the amazing features that Next.js has to offer, there’s one in particular that is very powerful and amazing: the ability to use different pre-rendering techniques.
We can work with server-side rendering (SSR) and static site generation (SSG) in the same Next.js application. We can also have our server-side rendered application and still have some generated pages inside.
In this article, we’re going to learn more about how these two pre-rendering techniques work and how we can use them. We’re also going to learn more about the specific use cases for both of them.
Modern JavaScript frameworks have made our lives as developers much easier. We can create powerful, rich web applications using many different rendering techniques.
You have probably heard of single page applications before. A single page application is an application that is rendered at the client side, even if the data might be fetched from the server.
Server-side rendering (SSR) is the exact opposite of this. SSR describes the process of pre-rendering the page on the server, which is then generated upon each user request.
When the pre-rendered page reaches the browser, JavaScript code is run to make the page interactive. This whole process makes the load speed faster. It also makes it easier and preferable to use server-side rendering for applications that depend on SEO.
Next.js does this work out of the box. By default, it will try to detect which pre-rendering technique your application is using and fetch your data.
Years ago, before JavaScript became so mature and powerful, developers usually returned HTML files based on HTTP calls. It was a very common technique to process the response on the server side using a server-side language (usually PHP) and returning a static HTML file.
Static-generated websites are nothing new for developers. We have been building them since the beginning of the web. Building rich web experiences can be hard, but with Next.js we can do so easily.
Next.js has introduced us to a better way of building static-generated websites with more dynamic performance.
SSG describes the process of building websites that render at build time. The output is an HTML file, assets such as JavaScript and CSS, and a few other static files.
When using SSG with Next.js, the page is pre-rendered at compile time. That means that the user won’t have to wait for the page to load at the browser; the page will simply be rendered.
For data fetching, Next.js provides three different functions:
getStaticProps
 : The page will be pre-rendered at build timegetServerSideProps
:  The page will be pre-rendered at runtimegetStaticPaths
 : This function generates a list of pages that will be pre-rendered at build timeThe biggest disadvantage of using SSG is that the build time can get very long. You won’t have a problem when you have only a few statically-generated pages, but as your application grows, the build time will increase.
The worst case scenario is when you have hundreds of statically-generated pages. The build time will take a long time, and if you have dynamic content on those pages, you can end up with too many builds.
Next.js makes it really easy for us to pick the correct pre-rendering technique for each page.
Remember, we learned that Next.js does static site generation by default. It simply works out of the box. However, it will try to detect which pre-rendering method you’re using for each page.
There are definitely some cases in which choosing the right function for fetching your data will make a difference. It should be taken in consideration because it can cause a huge impact on the user experience.
Imagine that we are creating a simple blog website. The content will be totally statically-generated, right? We will have only a few pages and the content will be fetched from our server, so it makes total sense to go with SSG in this case.
We can assume that every blog has at least two essential pages:
posts
 : This page is responsible for fetching and displaying all the blog postspost
 : This page is responsible for fetching and displaying a specific blog postIn our posts
page, we can use SSG for fetching all of our blog posts, using the getStaticProps
function:
export default function Posts({ posts }) { return ( <div> <h1>My latest posts</h1> {posts.map((post) => ( <h2>{post.title}</h2> ))} </div> ); } export async function getStaticProps() { return { props: { posts: await fetchPosts(), } }; }
The fetchPosts
function will be run at build time. It will fetch our external data and pre render the content for our page.
Now, imagine that we want to add a little feature on our simple blog website.
Imagine that we’re a very famous person and our followers want to buy items from our store. We want to turn our simple blog website into an ecommerce application and start to sell items such as shirts, mugs, and stickers.
All the components that an ecommerce application contains can use the same pre-rendering technique. We can use the getStaticProps
function for fetching our data at build time.
Now, what could we use for our checkout session? Every ecommerce application must have a checkout session where the user will conclude their purchase. In this case, the data can be fetched from our database at build time, but it can be different for each user.
We can use client-side fetching on top of SSG for rendering our page. We can create our simple component without using any function for fetching data at build time, and inside our component, we can use a data fetching library such as SWR for fetching our data on the client side:
import useSWR from "swr"; export default function CheckoutSession() { const { data, error } = useSWR("/api/checkout", fetcher) if (error) return <div>Something went wrong...</div> if (!data) return <div>Loading...</div> return <div>Cart: {data.items.length}</div> }
Using server-side rendering with the getServerSideProps
can do the job most of the time. The problem is that you might lose some features and advantages that SSG brings to Next.js applications. The page will be pre-rendered on each request using the data returned. Another disadvantage is that you can’t use a [fetch()](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch)
method to call an API inside the getServerSideProps
.
The fact that the page is pre-rendered at build time increases the performance and makes the page feel faster.
The HTML can also be fetched by a CDN server, which can also improve performance.
Another nice feature of SSG is that there’s no need to make a request to the database. The page is pre-rendered at build time, so even if your database goes down, you will still be able to render your page.
Next.js has become one of the most important React frameworks in the web community. It brought to developers so many features for helping to build more complex and rich websites.
Using a framework like Next.js allows us to use different pre-rendering techniques on our applications. We can use static site generation for something more simple and non dynamic. We can use server-side rendering for dynamic content and more complex pages. With these tools, building rich web applications has become easier and more fun.
Debugging Next applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your Next.js app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.
The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.
Modernize how you debug your Next.js apps — start monitoring for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowJavaScript’s Date API has many limitations. Explore alternative libraries like Moment.js, date-fns, and the new Temporal API.
Explore use cases for using npm vs. npx such as long-term dependency management or temporary tasks and running packages on the fly.
Validating and auditing AI-generated code reduces code errors and ensures that code is compliant.
Build a real-time image background remover in Vue using Transformers.js and WebGPU for client-side processing with privacy and efficiency.