31 March 2022
SSR vs SSG in Next.js – a practical overview for CTOs and devs
There are so many fancy buzzwords in the world of software development that if I really wanted to, I could fill an entire article with nothing but a long list of pretty terms found at the crossroad between software technology and business.
But SSR and SSG really do stand out. They are of genuine and practical importance to everyone involved in making software – from developers to designers, UX specialists, testers, web analysts, SEO experts, copywriters, and more.
The benefits of using SSR and SSG skilfully make a big difference in all of these areas. If you don’t know that yet, I’m sure you will see that by the time I wrap things up.
Today, you’re going to receive practical insights into SSR and SSG. I’m also going to introduce a framework that makes it really easy to use them – Next.js.
What this article is:
- an overview of key SSR and SSG concepts and benefits of both approaches,
- an introduction to Next.js and its benefits for business,
- a practical and easy-to-understand explanation of how to implement SSR and SSG using Next.js,
- a business case for and against using SSR and SSG with Next.js in specific scenarios (pros, cons, and alternatives).
What this article is not:
- an in-depth technical analysis of Next.js-based SSR and SSG.
While I’m going to get into some code, I’m keeping things approachable for a wider audience. I really do want to show how SSR and SSG relate to the needs of businesses in modern-day software development.
A quick look at history – why do we need SSR and SSG?
To understand the need for SSR and SSG more clearly, let’s take a big jump into the past. It’s a rainy and cold afternoon on December 4th, 1995…
… Single Page Applications
The native-like performance made SPAs popular in modern web development of that time, but not everyone shared the enthusiasm. SPAs certainly did not earn the love of search engines.
The HTML content rendered on the client-side was only available to them after it was executed in the browser. As a result, automatic crawlers dispatched by search engines missed most of it. That made SPAs very difficult to index properly. In turn, websites missed out on a large share of their organic traffic – a big problem in terms of search engine optimization. Just when SEO was really getting big in the first decade of the 21st century…
SSR vs SSG – the theory
SSR and SSG are the two techniques that evolved in the software development community to solve this very problem. What are they?
What is Server-Side Rendering?
Before Next.js showed up, such a process required a lot of tweaking and came with issues related to server load, on-demand content, caching, or even the application architecture itself. Don’t get me wrong – it was certainly doable, but it would have been nice if one could dedicate all that time to developing business logic instead…
What is Static Site Generation?
The major difference between SSR and SSG is that in the latter’s case, rather than during runtime, your HTML is generated during build time. Such websites are extremely fast since the HTML content is served even before you make a request. On the other hand, the website needs to be rebuilt and reloaded entirely every time a change is made. Consequently, SSG-based websites are far less interactive and native-like than those that rely on SSR. They are largely static sites with little to no dynamic content.
SSR and SSG – main benefits
Both SSR and SSG share some major benefits for business.
Easily indexable pages translate into measurable benefits – more organic traffic. For web apps that consider SEO strategically important for marketing and sales, it can further translate into improved profits and overall bottom line. Reports by SEO agencies confirm that – this e-commerce store improved its sales by 67% due to an increase in organic traffic.
UX research by Google shows that the bounce rate increases by as much as 32% if the page load goes from 1 to 3 seconds. SSR and SSG both improve the loading time when compared to rendering on the client-side. That’s because SSR collects all the data without waiting for the browser to make such a request. SSG goes even further, collecting all the data as it builds the application.
By doing more of the heavy lifting on the server rather than in the browser, you relieve client browsers and devices. As a result, SSR and SSG make it easier for users with older devices and slower internet connections to view a web app. This may be especially important for mobile conversions – as many as 73% of mobile users struggled with slow-to-load websites.
By now, I’m sure you can see the benefits of SSR and SSG. Let’s find out how you can realize them using Next.js.
Server-side rendering with Next.js
SSR pages are generated upon each request. The logic behind SSR is executed only on the server-side. It never runs in the browser.
How does Next.js-based SSR work?
If you export a function called getServerSideProps from a page, Next.js will pre-render this page on each request using the data returned by getServerSideProps.
The getServerSideProps method is called in two scenarios:
- when a user requests the page directly,
- or when a user requests the page in a client-side transition by using next/link or next/router.
The getServerSideProps function always returns an object with one of the following properties:
- props – contains all the data required to render a page.
- notFound – allows the page to return a 404 status and a 404 Page.
- redirect – allows redirecting the user to internal and external resources.
Interestingly, Next.js claims that the entire code used in getServerSideProps should be eliminated from the client bundle so that you can create server-related and client-related code. It’s not always true. One wrong import or dependency and the bundling will not work as expected. The more straightforward the structure of your application is, the easier it is to achieve proper behavior.
An implementation example:
As you can see, it’s quite self-explanatory:
- you can handle a query parameter such as a category
- if the category parameter does not exist, you can return a redirect object. Next.js will redirect the user to a given destination
- if the parameter exists, you can proceed and make a call to an external API
- if the data from the external API is empty, you can return a 404 page
- if you have any item, you can properly render your page with a list of products
When to use SSR?
SSR is recommended for apps in which you have to pre-render frequently updated data from external sources. This technique is especially recommended when the data cannot be statically generated before a user request takes place, and at the same time needs to be available to search engines.
Example? A search results page or an e-commerce website that includes user-generated content.
Pros of SSR:
- the page always contains up-to-date content,
- if an error is thrown inside getServerSideProps, Next.js will show an error page automatically
- you have access to cookies, request headers, and URL query parameters
- you can implement logic related to the 404 page, and redirects based on user requests and data
Cons of SSR:
- the page is noticeably slower than a statically generated one because some logic needs to be executed on every request (e.g. API call).
- a server-side rendered page can be cached on CDNs only by setting the cache-control header (it requires additional configuration).
- the Time to First Byte (TTFB – the time it takes a server to deliver the first byte of information to a page) metric will be higher than on a statically generated page
Static site generation with Next.js
SSG-based pages are generated at a build time. You can reuse (and reload) the whole page on each request.
How does Next.js-based SSG work?
Next.js pre-renders pages using static generation, which among other things means that it doesn’t fetch any data by default. If you need to generate a page that includes such data, you have two scenarios:
- if the page content depends on external data, use getStaticProps method only,
- if page paths depend on external data – use the getStaticPaths method in addition to getStaticProps.
The getStaticProps method always runs on the server (as opposed to the client-side). It collects page data for a given path. The method is called in one of three cases:
- during the next build,
- in the background when you use revalidate,
- on-demand in the background when using unstable_revalidate.
I’m going to go over the last two cases in the Incremental Static Regeneration section.
The getStaticProps method always returns an object with one of the following properties:
- props – it contains all the data required to render a page,
- notFound – allows the page to return a 404 status and a 404 Page,
- redirect – allows redirecting the user to internal and external resources,
- revalidate – the time (in seconds) it takes for a page regeneration to occur.
What is really important, the getStaticProps method does not have access to incoming requests. If you need access to the request, consider using some middleware besides getStaticProps or consider SSR instead. In development mode, getStaticProps is called on every request for a better developer experience.
The getStaticPaths method will only run during a production build. It will not be called during runtime. The method must be used together with getStaticProps. It cannot be used with getServerSideProps.
The getStaticPaths method always returns an object with any of the following properties:
- paths – determines which paths should be pre-rendered at build time,
- fallback – a boolean flag that determines how the app should behave in case the user wants to visit a page that wasn’t listed in the paths array.
Next.js has provided On-demand Static Regeneration in version 12.1. It means that you can manually purge the static cache for a page if its content has been updated in an external source (e.g. a CMS or database). The function gives you the ability to always deliver up-to-date data with no delays and still keep static generation for a given page.
An implementation example:
The example above implements the second of the two previously mentioned scenarios – when paths depend on external data:
- you implement the getStaticPaths method to get a list of blog posts based on a response from an external API,
- the getStaticPaths method returns an array of paths, which will be pre-rendered at build time,
- fallback: false returned by getStaticPaths means that application will present a 404 page for any page not listed in paths,
- with the getStaticProps method, you fetch the data for a single post and return the data as page props to a page component.
When to use SSG?
SSG is recommended for use on any page where you have to pre-render data. It can be generated before a user request takes place. It means that your data is available at build time, or in other words – on every page where you want to present static content or provide excellent SEO capabilities. Examples of such pages include blogs or marketing sites that contain data from a headless CMS the content of which is not updated very often.
Pros of SSG:
- you can boost performance using CDN caching without a lot of extra configuration,
- your static page is always online even if your backend or data source goes down,
- your page is much faster than a server-side rendered one because the entire logic was executed at build time
- your backend serves only static files, which contributes to decreasing the server load,
- you can run your statically generated page in the preview mode; the page is then rendered at request time.
Cons of SSG:
- due to the lack of access to incoming requests, you cannot read request headers, cookies, or URL query parameters,
- your content cannot be changed between site deployments (without ISR).
As you can see, both SSR and SSG have their pros and cons. That’s why you should also consider alternative solutions.
SSR and SSG alternatives
With all that said and done, one could think that SSR and SSG exhaust all the possibilities of moving your workload to the server. But developers really can’t help themselves but innovate all the time. Enter Incremental Static Regeneration!
Incremental Static Regeneration – what is it?
Incremental Static Regeneration is one of the most powerful features in Next.js. It enables you to use static generation in a way that doesn’t force you to rebuild your whole page on every reload.
Incremental Static Regeneration is an extension of static generation. It offers an additional property returned by getStaticProps. This property is called revalidate and is counted in seconds. The revalidate property tells you how often Next.js should regenerate static HTML for a given page. The regeneration is triggered by a user request rather than every “X” seconds. That property should be configured based on the nature of data you want to show on a given page like blog posts, products, user-related data, marketing content.
ISR improves the scalability of web applications. You can statically generate hundreds of the most popular or latest posts at build time and enable ISR for the rest of the articles. Once a user makes a request to a page not listed in getStaticPaths, Next.js will server-render the page for that user and statically generate the page in the background. The next user will receive statically generated content. It will keep the build time short, retaining all the benefits of SSG for every blog post. Imagine how long the build can take if you want to pre-render 10,000+ posts at build time!
An implementation example:
How does it work?
You fetch a list of posts to pre-render in getStaticPaths the same way you do for SSG. The difference is that the fallback value is set to blocking. It means that when the user visits a page that is not statically generated yet, Next.js will return content after server-rendering is done for that particular page. SSG will be done in the background for the users that follow.
In getStaticProps, there is a new property tasked with returning an object called revalidate. It is counted in seconds so Next.js will pre-render the page when a request comes, but at most once per 10 seconds. When a page is visited just once per day, revalidation triggers once per day.
When can you consider ISR?
- When you want to refresh the content without having to rebuild the entire site.
- When you have hundreds of pages that you have to pre-render but you do not want to spend hours on building an app.
- When you want to statically generate pages that will be created soon – for example by content editors. When they create a new blog post, you do not want to have to rebuild your app to generate a newly created page.
CSR or Client-Side Rendering
Client-side rendering based on client-side data fetching is another alternative. Unlike in the case of working with server-side rendering API, data fetching can be done on a page or component level.
Client-side data fetching can affect the performance of your application and the loading speed of your pages negatively. That’s because data fetching is done when the component or page is mounted and the data is not cached.
When can you consider CSR?
- when your acquisition strategy doesn’t prioritize SEO,
- when you don’t need to pre-render your data,
- when the content of your pages needs frequent updating,
- when the page content is related to logged user data.
When you do decide to use SSR or SSG, should you always implement it using Next.js?
Why Next.js for SSR and SSG?
Next.js is a powerful solution that offers a lot of different approaches to rendering your pages. Since it covers so many techniques, you can easily adjust it to your requirements. You can use different rendering strategies or even combine them together.
How to figure out which approach is the best? Here are a couple of helpful questions to ask yourself:
- Do you need both excellent SEO and performance? SSG is the way to go.
- Do you need SEO and the ability to frequently update your data? Use SSR.
- Do you require frequently updated data but SEO and performance are not a priority? CSR is a sensible choice.
- Do you have thousands of pages that require frequent updating of content? ISR is worth considering.
There are multiple combinations of the scenarios above. Finding the best balance requires a thorough analysis of your project.
Another reason for choosing Next.js is straightforward, and yet very important – Next.js is extremely popular. It has over 83,000+ stars on Github (more than any other alternatives) and a big community. Furthermore, the Vercel company works in the background, ensuring continuous development and maintenance of the Next.js framework. Combined, you have a stable, vibrant environment that is here to stay.
Of course, as with every framework, Next.js has its disadvantages. For example, it’s not that easy to share static content between multiple Next.js instances in the production environment using Docker. Still, the flexibility of Next.js makes it relatively easy to find solutions in the event of changing requirements in the project. Most likely you won’t need to migrate to another solution or framework to get things done.
Gatsby is a fast and flexible framework and static site generator that makes building websites with any CMS or API very pleasant. Gatsby is used for building websites that generate static HTML at build time. Subsequently, the code can be stored on CDNs and distributed to all of your users across the world. Gatsby combines the best elements of React and GraphQL so it is quite a treat for developers. Since version 4, Gatsby supports SSR, which makes the framework even more powerful. Over 52,000 GitHub stars are a testament to Gatsby’s popularity.
Remix is a full-stack web framework that lets you focus on the user interface. It delivers a fast and slick user experience.
In Remix, there are only two rendering modes – you can use SSR at runtime and CSR at runtime. Remix uses React to build user interfaces. It contains nested routes, which means that you can run multiple routes on a single view without any loading states. Remix has become popular in the last few months. The community is growing fast. It has over 14,000 GitHub stars.
Hexo is a Node.js-powered tool that belongs to the SSG category. It’s fast, simple and powerful. It is marketed primarily as a blog framework. It parses your posts with Markdown or another render engine, generating static files in a matter of seconds. It has over 34,000 stars on GitHub.
What will the future bring?
Since the roadmap for Next.js is not publicly available (with the exception of milestones), it’s not that easy to predict what it will look like in the future. But there is at least one upcoming feature that is really worth mentioning. At this point, it’s only available in the alpha version.
React Server Components
React 18 is introducing the React Server Components. It doesn’t sound so exciting at first glance, but it gets better! Let’s start with the docs.
In short, server components and their logic will be executed only on the server. They will not be subject to the hydration process. As a result, you will deliver freshly generated static pieces of HTML. Naturally, server components can contain child components of the client. These components can be fully interactive and hydrated in the browser.
To understand the practical implications, let’s fall back on the docs again.
Despite being in the alpha version, server components already support some of Next.js APIs, including next/link, next/image, and next/document. They have access to the router as well. It is injected into the component by the router prop.
Server components are stateless, so you cannot use React Hooks as useContext, useState, useReducer, or useEffect inside these components. Additionally, features such as i18n (internationalization) or next/head are not supported yet.
It all sounds great in theory, but I’m still going to wait for the official release of server components to do proper trials and decide about them. I’m going to pay attention to the way they influence user experience, web performance, and existing Next.js workflows.
Wish to learn more about React Hooks? Check out this React Hooks vs Redux overview
SSR/SSG to improve and future-proof your application
And that’s it! Of course, there is a lot more to be said about SSR and SSG. However, this should give you a solid understanding of how it works to make both your application and your business more efficient. You know that:
- SSR and SSG are techniques that evolved organically to respond to real issues that businesses faced with the performance, usability, and visibility of their content.
- SSR improves all of these metrics without compromising your web application in any way.
- SSG offers even more in terms of metrics improvement, but it’s not viable in many scenarios – the need to rebuild your website every time the data changes limits its use cases to pages that do not include a lot of data exchange with the client.
- Next.js offers a solution to some of the SSG issues with Incremental Static Regeneration. Of course, this solution comes with its own package of pros and cons. The latter include issues with caching and debugging.
If you have any experience with software development, either as a dev or business person, you definitely realize that there are no one-size-fits-all solutions. The more complex and innovative your application is the more truth that thought holds. The same thing goes for SSR and SSG.
The devs at The Software House have plenty of experience with SSR and SSG. They know how and when to use it.
If you think these solutions might be just what you need, contact us to consult your project.