Next.js Rendering Strategies: SSR, SSG, and ISR Compared
Next.js is currently the most popular React-based framework, it provides a powerful set of features for building modern web applications. Its tools range from image optimization to routing. However, one of the key advantages of Next.js is its ability to support different rendering strategies, each with their pros and cons.
Server-Side Rendering (SSR), Static Site Generation (SSG), and Incremental Static Regeneration (ISR). In this article we will look over these 3 methods and discuss their potential benefits and drawbacks.
The importance of rendering strategies
The React front-end framework excels for building Single Page Applications (SPA), and performs as an optimized way to build javascript web apps.
By default, React apps download a large JavaScript bundle that the browser must execute before displaying the page to users. While it is possible to split the bundle into smaller files, this still requires downloading and executing code.
Traditionally, web technologies would send HTML and CSS directly to users, but this approach has largely been supplanted by SPA frameworks like React, Angular, and Vue. However, some argue that this is a step backwards in terms of user experience.
While the vanilla React client-side rendering is already largely optimized for most use cases, in some very-specific set of constraints it could use further optimization. This is where frameworks that offer Static Generation, like NextJS, can be particularly helpful.
Server-Side Rendering (SSR)
We're talking about SSR whenever your web application is to be rendered on the server before it is sent to the client. The HTML markup and JS code is generated on the server before being sent to the client browser as an HTML document every time the client requests it.
In order to implement SSR for a web page, it is necessary to create an asynchronous function called getServerSideProps
on a page's file and export it. This function will then be invoked by the server upon every request.
export default function Page({ data }) {
// Render your page...
}
// This gets called on every request
export async function getServerSideProps() {
// Fetch data from external API
const res = await fetch(`https://.../data`)
const data = await res.json()
// Pass data to the page via props
return { props: { data } }
}
Finally, NextJS needs to hydrate the page. Hydration is the process where React needs to connect the DOM with the virtual DOM it created. This process allows React to synchronize and update the rendered components, ensuring a seamless interactive experience for users.
Pros
- Improved SEO: With this approach, search engine crawlers will have easier access to the complete HTML markup with the populated data.
- Better accessibility: You can guarantee that content of the page will be rendered the same for your users, regardless of their device or browser.
Cons
- Increased server load: As SSR generates web pages on the server, it demands extra server resources to carry out the rendering process, which can lead to higher server load and cause slower response times for users.
- Slower page loads: Each time a page is requested, the server needs to process the necessary data, render the HTML, and send it back to the client. This additional step in the rendering process can introduce latency and result in slower page load times compared to other approaches.
- Slower page transitions: Page transitions may be slower since each page request requires a new server response.
- More difficult to debug: Debugging pages can be more difficult compared to debugging client-side rendered pages because the code is executed on both the server and the client
Static Site Generation (SSG)
SSG is a rendering strategy that generates HTML markup at build time instead of on the server or client. With SSG, the HTML markup for each page is pre-generated and served to the client as a set of static files.
Next.js uses SSG by default when your pages do not need external data.
function Home() {
return <div>Hello World!</div>
}
export default Home
If your page needs external data, you need to add getStaticProps
to your page. This function will run at build time and pass the relevant props to your component.
export default function Recipes({ recipes }) {
// Render recipes...
}
// This function gets called at build time
export async function getStaticProps() {
// Call an external API endpoint to get recipes
const res = await fetch('https://.../recipes')
const recipes = await res.json()
// The Recipes component will receive `recipes` as a prop at build time
return {
props: {
recipes,
},
}
}
In addition to using getServerSideProps
, SSR in Next.js also provides the ability to pre-render dynamic paths using getStaticPaths
. With getStaticPaths
, you can specify a list of paths that should be pre-rendered at build time, which can help to improve the performance of your Next.js application by reducing the number of server requests required.
// This function gets called at build time
export async function getStaticPaths() {
// Call an external API endpoint to get products
const res = await fetch('https://.../products')
const products = await res.json()
// Get the paths we want to pre-render based on products
const paths = products.map((product) => ({
params: { id: product.id },
}))
// We'll pre-render only these paths at build time.
// { fallback: false } means other routes should 404.
return { paths, fallback: false }
}
Pros
- Faster page loads: Since the HTML markup is generated at build time, SSG can provide faster page loads compared to both client-side rendering and SSR.
- Better security: With SSG, there is no need to execute any server-side code, reducing the risk of server-side attacks.
- Lower server overhead: This approach eliminates the need for complex server-side rendering, reducing the server overhead required to handle page requests.
Cons
- Limited support for dynamic content: Since SSG generates HTML markup at build time, it may not be suitable for content that changes frequently, such as user-generated content or real-time data.
- Longer build times: SSG requires additional build time to generate the HTML markup for each page, which may increase the overall build time for your Next.js application.
- Complex build processes: It can be more complex to implement for pages that require dynamic content. For example, if your Next.js application relies on external data sources or APIs to generate content, you may need to build custom build scripts or serverless functions to handle the generation of HTML markup for each page. This can lead to a more complex build process that requires additional development time and resources.
Incremental Static Regeneration (ISR)
Next.js 9.5 introduced Incremental Static Regeneration (ISR) as a rendering approach that offers the advantages of both SSR and SSG. With ISR, Next.js pre-generates static HTML markup at build time and incrementally re-generates the markup on the server at runtime as needed.
Unlike SSR, ISR allows for the re-generation of only the specific pages or sections of pages that have changed or expired, providing quicker updates and reducing server overhead.
Next.js implements ISR through the revalidate
option in the getStaticProps
function, which determines how frequently the HTML markup for a page should be re-generated on the server, in seconds. When a page is revalidated, Next.js re-generates the HTML markup, serving up-to-date content to the client without needing to rebuild the entire application.
export async function getStaticProps() {
const res = await fetch('https://.../recipes')
const recipes = await res.json()
return {
props: {
recipes,
},
// Next.js will attempt to re-generate the page:
// - When a request comes in
// - At most once every 30 seconds
revalidate: 30, // In seconds
}
}
Pros
- Faster updates: With ISR, only the specific pages or sections of pages that have changed or expired are re-generated on the server, resulting in faster updates compared to SSR and reduced server overhead compared to full SSG rebuilds.
- Improved performance: Since pages are initially pre-rendered as static HTML markup, it can provide the faster initial page loads and improved SEO benefits of SSG.
- More dynamic than SSG: While SSG is suitable for static content, ISR can provide a more dynamic experience by enabling pages to be incrementally updated as needed, providing the best of both worlds for applications with frequently changing content.
Cons
- Longer build times compared to SSR: While ISR provides faster updates compared to SSR, it can be slower to build than SSR since it generates static HTML markup at build time unless you manually opt out of it.
- Potential for stale content: Since the
revalidate
option specifies how frequently Next.js should re-generate the HTML markup for a page, there is a potential for stale content to be served to users if therevalidate
time is too long. - Server ressources: While ISR can provide faster updates and improved performance compared to SSR or SSG, it may require additional server resources to handle incremental re-generation of HTML markup
Conclusion
NextJS provides a variety of approaches to handle page rendering, from server-first to build-first, catering to the needs of different project structures.
SSR can provide improved SEO and easier implementation, but may require more server resources and causes slower page loads.
SSG can provide faster page loads and better scalability for static content, but may not be suitable for dynamic content or large data sets.
ISR can offer faster updates and improved performance for frequently changing content, but may require additional server resources and can potentially serve stale content.
Among these options, ISR stands out as a compelling choice when stale content is acceptable. By leveraging ISR, NextJS offers a unique capability to strike a balance between real-time updates and optimal performance.
In addition to the pros and cons discussed in this article, there are many more factors that can influence the choice of your rendering strategy in Next.js. For example, the specific technical requirements of your application, the available server resources and hosting options, and the expertise and preferences of your development team can all play a role in determining which strategy is the best fit.
By taking the time to fully understand the technical and business implications of each rendering strategy and exploring additional dive deeper points, you can make an informed decision that delivers the best possible user experience and performance for your Next.js application.