DEV Community

Cover image for Mastering Rendering: A Comprehensive Guide from React to Next.js
ShaneDushyantha
ShaneDushyantha

Posted on

Mastering Rendering: A Comprehensive Guide from React to Next.js

Unveiling the Evolution and Strategies of React Rendering, Vital for Next.js Development

Rendering is a fundamental process in web development, serving as the bridge between the code you write and the user interfaces you create. In the context of Next.js, mastering the art of rendering is paramount to crafting high-performance applications.

Perhaps you’ve encountered terms like CSS, SSR, and RSCs in your journey, finding them a tad bewildering. Fear not, for this article aims to demystify these concepts, providing comprehensive insights to illuminate your path.

However, before delving into the intricacies of Next.js, it’s imperative to grasp the fundamentals of rendering in React. Let’s embark on this enlightening journey together.

Exploring the Evolution of React Rendering: A Prelude to Next.js Mastery

Understanding the nuances of rendering in Next.js is greatly facilitated by tracing the evolution of React’s rendering strategies over the past decade. Let’s embark on a journey through React’s evolutionary path, beginning with its inception as the go-to library for crafting single-page applications (SPAs).

In the realm of SPAs, a typical interaction unfolds as follows: when a client initiates a request, the server responds by dispatching a solitary HTML page to the client’s browser. This HTML page conventionally comprises a straightforward div tag along with a reference to a JavaScript file.

Upon receipt, the HTML file triggers the download of the associated JavaScript file. Subsequently, the client’s browser dynamically generates HTML content based on the instructions encapsulated within the JavaScript file. This freshly minted HTML is then seamlessly integrated into the Document Object Model (DOM) under the root div element, thereby presenting the interface to the user within the browser environment.

Client Side Rendering

This phenomenon becomes apparent when inspecting the DOM, where the dynamically generated HTML is observable, contrasting with the static HTML presented through the “view source” option, which reflects the initial HTML sent by the server to the browser. This rendering methodology, characterized by the transformation of component code into a user interface directly within the client’s browser, is commonly referred to as “Client-Side Rendering (CSR).”

CSR swiftly gained traction as the de facto standard for SPAs, garnering widespread adoption within the developer community. However, it wasn’t long before developers started to discern certain inherent drawbacks associated with this approach.

The drawbacks of Client-Side Rendering (CSR)

  1. SEO Implications:
    CSR often results in HTML generation dominated by a single div tag, which is suboptimal for SEO. Search engines rely on the content within HTML to index web pages effectively. However, with CSR, the initial HTML content lacks substantive information, potentially hindering search engine crawlers’ ability to index the page comprehensively. Moreover, the large bundle size and sequential network requests for API responses from deeply nested components can further exacerbate this issue. As a result, critical content may not render swiftly enough for search engine crawlers, impacting the page’s visibility in search engine results.

  2. Performance Concerns:
    Entrusting the client’s browser with the entirety of the workload, from data fetching to UI computation and interactivity, can lead to performance bottlenecks. Users may experience delays during page load, encountering blank screens or loading spinners as the browser processes the JavaScript bundle. With each new feature added to the application, the size of the JavaScript bundle tends to increase, prolonging the wait time for users to perceive the UI. This delay is particularly noticeable for users with slower internet connections, detracting from the overall user experience.

While Client-Side Rendering laid the foundation for the interactive web applications prevalent today, the need to address SEO and performance limitations prompted developers to seek alternative solutions.

Server-Side Rendering (SSR): Addressing the Limitations

In response to the limitations of Client-Side Rendering (CSR), modern React frameworks such as Gatsby and Next.js have embraced server-side solutions, notably Server-Side Rendering (SSR). This paradigm shift fundamentally alters the approach to content delivery, offering solutions to the challenges posed by CSR.

With SSR, when a user initiates a request, instead of receiving a sparse HTML file reliant on client-side JavaScript for page construction, the server assumes responsibility for rendering the complete HTML content. Subsequently, this fully formed HTML is transmitted directly to the browser. By generating HTML on the server side, the browser can swiftly parse and display it, markedly enhancing the initial page load time.

Non Interactive Response

Server-Side Solutions: Enhancing Visibility and User Experience

Server-Side Rendering (SSR) offers compelling advantages, particularly in addressing the limitations of Client-Side Rendering (CSR):

  1. Improved SEO:
    SSR dramatically enhances search engine optimization (SEO) by providing search engines with readily indexable server-side-rendered content. Unlike CSR, where initial HTML content may be sparse, SSR ensures that search engines can efficiently crawl and index the complete content, thereby boosting the visibility of web pages in search engine results.

  2. Enhanced User Experience:
    With SSR, users are presented with the page’s HTML content immediately upon accessing the site, eliminating the need for prolonged loading times characterized by blank screens or loading spinners. This swift delivery of content fosters a seamless user experience, minimizing user frustration and increasing engagement.

However, it’s essential to acknowledge that while SSR excels in improving content visibility, it introduces complexities, particularly concerning page interactivity. Unlike CSR, where client-side JavaScript handles UI interactions post-rendering, SSR necessitates careful management of server-side logic to ensure smooth interactivity without compromising performance.

In navigating these complexities, developers must strike a balance between server-side rendering for SEO benefits and client-side interactivity for a responsive user experience, ensuring optimal performance and usability across all facets of web development.

Hydration: Breathing Life into Static Content

The culmination of a user’s interaction with a webpage is momentarily deferred until the browser completes the download and execution of the JavaScript bundle, encompassing React and application-specific code. This pivotal phase, known as hydration, serves as the transformative bridge from static HTML to dynamic interactivity.

Hydration

During hydration, React assumes control within the browser environment, orchestrating the reconstruction of the component tree in memory based on the static HTML initially served by the server. Methodically, React imbues this tree with interactive elements, meticulously positioning them to facilitate seamless user engagement. Subsequently, React proceeds to bind the requisite JavaScript logic to these elements.

This intricate process encompasses several key steps, including the initialization of application state, attachment of event handlers for actions such as clicks and mouse movements, and the configuration of any other dynamic functionalities essential for a fully immersive user experience.

Understanding the concept of hydration is foundational to mastering rendering within Next.js, underpinning the transition from static content to dynamic, interactive web applications.

Categorizing Server-Side Solutions: Strategies for Dynamic Content

Server-side solutions can be classified into two primary strategies:

  1. Static Site Generation (SSG):
    SSG occurs during the build process, typically when the application is deployed on the server. This results in pre-rendered pages that are fully generated and ready to serve. It’s particularly advantageous for content that remains static or changes infrequently, such as blog posts.

  2. Server-Side Rendering (SSR):
    SSR dynamically generates pages in response to user requests, computing HTML content on the server and sending it to the client. This approach is suitable for personalized content, such as social media feeds, where the HTML output depends on the logged-in user’s context.

While SSG and SSR serve distinct purposes, they are often collectively referred to as server-side rendering (SSR). Server-Side Rendering represents a significant advancement over client-side rendering, offering faster initial page loads and improved search engine optimization (SEO). However, it also introduces its own set of challenges.

Drawbacks of Server-Side Rendering (SSR): All-or-Nothing Waterfall

While Server-Side Rendering (SSR) offers notable benefits, it also presents several drawbacks that developers must contend with:

  1. Sequential Data Fetching:
    In SSR, all necessary data must be fetched before the server can begin rendering the page. Unlike client-side rendering, where components can start rendering and then pause or wait while data is fetched asynchronously, SSR requires completion of data fetching before any part of the page can be sent to the client. This sequential fetching process can delay the service response time to the browser, potentially impacting the overall user experience.

  2. Full Component Loading for Hydration:
    Successful hydration in SSR relies on ensuring that the component tree generated by the server precisely matches the component tree in the browser. Consequently, all JavaScript code for the components must be loaded on the client before any hydration can commence. This necessitates loading all components before initiating the hydration process, potentially leading to increased loading times and performance overhead.

  3. Sequential Hydration Process:
    React hydrates the component tree in a single pass during SSR, meaning that once hydration begins, it must proceed uninterrupted until the entire tree is hydrated. As a result, all components must be hydrated before any interaction can occur, leading to potential delays in user interactivity and responsiveness.

These challenges contribute to an all-or-nothing waterfall scenario, where the completion of each stage is contingent upon the previous one. This can result in inefficiencies, particularly if certain parts of the application, such as data fetching or component loading, are slower than others.

To address these limitations, the React team has introduced new and improved SSR architectures, aimed at optimizing performance and enhancing user experience. These advancements seek to mitigate the drawbacks of SSR while leveraging its benefits for building dynamic and interactive web applications.

Suspense SSR Architecture: Enhancing SSR Performance

In React 18, the introduction of the Suspense SSR architecture aims to address the performance drawbacks associated with traditional SSR. This architecture leverages the Suspense component to unlock two significant SSR features:

  1. HTML Streaming on the Server:
    Traditionally, SSR has been an all-or-nothing affair, where the server sends complete HTML to the client before React proceeds to hydrate the entire application for interactivity. With Suspense SSR, React introduces the capability for HTML streaming on the server. This means that React can start sending HTML to the client without waiting for the entire JavaScript bundle to be loaded, enhancing the initial page load performance.

  2. Selective Hydration on the Client:
    By wrapping specific parts of the page, such as the main content area, within the Suspense component, React can prioritize rendering and hydrating critical sections of the page. This selective hydration approach allows React to stream placeholder content, such as a loading spinner, while asynchronously fetching and hydrating the main content.

Hydration on the Client

As illustrated, traditional SSR follows an all-or-nothing approach. Initially, the server dispatches the entire HTML content to the client. Subsequently, the client awaits the complete HTML payload before parsing and rendering the content. Only after the JavaScript bundle is fully loaded does React commence the hydration process, dynamically adding interactivity to the entire application. This sequential process necessitates the client to receive and process the entire HTML payload before any interaction or dynamic functionality can be enabled.

Here is a similar visualization from a user interface perspective.

<Layout>
  <Header/>
  <Sidenav/>
  <MainContent/>
  <Footer/>
</Layout>
Enter fullscreen mode Exit fullscreen mode

similar visualization from a user interface perspective

The traditional approach to SSR follows a sequential process:

  1. Render all HTML on the Server:
    Initially, the server generates and sends the complete HTML markup to the client in response to a request.

  2. Receive HTML on the Client:
    The client receives and parses the HTML content, rendering the initial static layout visible to the user.

  3. Load JavaScript Bundle:
    Subsequently, the client downloads and loads the JavaScript bundle containing React and application-specific code.

  4. Hydrate the Entire Application:
    Upon loading the JavaScript bundle, React proceeds to hydrate the entire application, transforming the static HTML markup into interactive and dynamic components.

The traditional approach involves a sequential process where the server renders the complete HTML, which is then sent to the client for display.

However, React 18 introduces a novel capability. By encapsulating specific sections of the page, such as the main content area, within the Suspense component, we empower React to initiate HTML streaming for the remaining page content without awaiting the retrieval of data for the main section. This strategic use of Suspense optimizes the rendering process, allowing for faster streaming of HTML content and enhancing the overall performance of Server-Side Rendering (SSR) in React applications.

<Layout>
  <Header/>
  <Sidenav/>
  <Suspence fallback={<Spinner />}
    <MainContent/>
  <Suspence/>
  <Footer/>
</Layout>
Enter fullscreen mode Exit fullscreen mode

Suspence

With this approach, React streams a placeholder, such as a loading spinner, instead of waiting for the complete component to be rendered. Once the server retrieves the necessary data for the main section, React seamlessly injects additional HTML into the ongoing stream, along with minimal JavaScript required to position that HTML correctly. As a result, even before the entire React library is loaded on the client-side, the main section becomes visible to the user, enhancing perceived performance and addressing the initial problem of delayed content rendering in Server-Side Rendering (SSR).

HTML Streaming on the Server

  1. Immediate Content Display:
    With HTML streaming, there’s no need to fetch all data before rendering begins. This allows for immediate display of content to users, enhancing the perceived speed of page loading.

  2. Dynamic Integration:
    Sections of content that may cause delays in initial HTML rendering can be seamlessly integrated into the streaming process at a later stage. This ensures a smooth and uninterrupted user experience, even in the presence of slower-loading components.

  3. Suspense-driven Approach:
    The essence of HTML streaming lies in how Suspense enables server-side streaming. By strategically leveraging Suspense components, React can optimize the rendering process, prioritizing the delivery of critical content and enhancing overall performance.

While HTML streaming addresses the challenge of initial content delivery, we still face additional hurdles that need to be overcome to ensure optimal performance and user experience.

The challenges posed by server-side rendering (SSR) and client-side hydration are significant

  1. Delayed Hydration Start:
    Hydration of the client-side application cannot commence until the JavaScript bundle for the main section is fully loaded. This delay in hydration may lead to slower interactivity and a less responsive user experience.

  2. Large JavaScript Bundle:
    If the JavaScript bundle for the main section is extensive, it can significantly prolong the loading process, resulting in delayed rendering and interaction. This issue can be exacerbated in applications with complex features and functionalities.

To mitigate these challenges, code splitting techniques can be employed.

Code Splitting: Optimizing Application Loading

An effective strategy for optimizing application loading is through code splitting. This technique involves identifying specific segments of code that are not immediately required for initial loading and instructing the bundler to segregate them into separate script tags.

Utilizing React.lazy for code splitting provides a streamlined approach to separate section code from the primary JavaScript bundle. By implementing this approach, the JavaScript containing React and the code for the entire application, excluding the main section, can be downloaded independently by the client. This enables the client to fetch critical resources without waiting for the main section's code, thereby enhancing loading efficiency and expediting the delivery of essential content to users.

Selective Hydration on the Client: Enhancing Interactivity

The introduction of selective hydration in React offers a powerful mechanism to enhance interactivity while optimizing performance. By encapsulating specific sections within the <Suspense> component, React is instructed not only to stream content but also to selectively hydrate components as they become available.

import { lazy } from 'react';
const MainContent = lazy(() => import('./MainContent.js'));

<Layout>
  <Header/>
  <Sidenav/>
  <Suspense fallback={<Spinner />}>
    <MainContent/>
  </Suspense>
  <Footer/>
</Layout>
Enter fullscreen mode Exit fullscreen mode

With selective hydration, sections of the page can be hydrated independently as their respective JavaScript code becomes available, even before the complete HTML and JavaScript bundles are fully downloaded. This approach ensures a seamless user experience, where initially non-interactive content is streamed in HTML, followed by React hydration.

Thanks to selective hydration, heavy JavaScript code for the main section no longer hinders the interactivity of other page components. React intelligently manages the hydration process, prioritizing interactions with elements such as the header and side navigation, allowing users to engage with the page elements without waiting for the main content to be hydrated.

This automatic management of hydration by React ensures optimal performance and responsiveness, particularly in scenarios where multiple components await hydration. React prioritizes hydration based on user interactions, further enhancing the overall user experience.

Selective hydration not only addresses the necessity to hydrate everything before interaction but also streamlines the rendering process, enabling faster and more responsive web applications.

For instance,

React synchronous hydrate

in the case where the sideNav is about to be hydrated and a user clicks on the main content area, React synchronously hydrates the clicked component during the capture phase of the click event. This ensures that the component is ready to respond immediately to user interactions, prioritizing responsiveness and enhancing the user experience. The sideNav component is hydrated later on, ensuring smooth and efficient rendering.

While the suspense SSR architecture resolves key issues such as delayed hydration and inefficient rendering, additional challenges remain. These challenges may include managing complex data fetching logic, optimizing performance for large-scale applications, and ensuring compatibility with legacy systems and frameworks.

Drawbacks of Suspense SSR

  1. Increased Data Download: Despite asynchronous streaming of JavaScript code to the browser, users still need to download the entire codebase for the webpage. As applications grow in complexity and feature richness, the amount of data users must download also increases.
    This raises the crucial question:
    should users be required to download such a significant volume of data?

  2. Inefficient Hydration Process: The current approach mandates that all components undergo hydration on the client side, regardless of their actual need for interactivity. This inefficiently consumes resources and prolongs loading times and time to interactivity for users. Devices are burdened with processing and rendering components that may not even require client-side interaction.
    These challenges prompt a key question:
    should all components be hydrated, even if they don’t need interactivity?

  3. Heavy Client-Side Processing: Despite servers’ superior capacity for intensive processing tasks, the bulk of JavaScript execution still occurs on the user’s device. This can lead to performance degradation, particularly on less powerful devices.
    This raises concerns about:
    Whether so much computational work should be delegated to the user’s device.

Before we go forward, Lets recap what we done so far,

Recap: The Evolution of React

Throughout this article, we’ve traced the evolution of React’s rendering strategies, from client-side rendering (CSR) to server-side rendering (SSR), and ultimately to Suspense for server-side rendering. Each stage of this evolution has introduced improvements, along with its own unique challenges.

  1. CSR → SSR → Suspense SSR:
    We’ve witnessed React’s journey from client-side rendering to server-side rendering, culminating in the adoption of Suspense for server-side rendering. Each transition has marked a step forward in optimizing rendering performance and enhancing user experience.

  2. Challenges:
    Despite the advancements brought about by Suspense for SSR, significant challenges remain:

    • Increased bundle sizes leading to excessive downloads for users.
    • Unnecessary hydration, which delays interactivity.
    • Extensive client-side processing, potentially resulting in poor performance.

Addressing these challenges requires more than incremental steps; it demands a substantial leap towards a more robust and efficient solution.

  • Introduction of React Server Components: To overcome these challenges, React Server Components emerge as a promising solution. This innovative approach represents a significant advancement in React’s rendering capabilities, offering a powerful solution to optimize performance and streamline rendering processes.

React Server Components (RSC): A Paradigm Shift in React Architecture

React Server Components (RSC) herald a new era in React architecture, conceived and crafted by the React team. This innovative approach seeks to harness the inherent strengths of both server and client environments, with a primary focus on optimizing efficiency, load times, and interactivity.

At the heart of this architecture lies a dual-component model, which distinguishes between server components and client components. Unlike traditional classifications based on component functionality, this differentiation is predicated on where the components execute and the specific environments they are designed to interact with.

By delineating between server and client components, React Server Components revolutionize the rendering process, enabling a seamless interplay between server-side and client-side execution. This nuanced approach not only enhances performance but also facilitates a more granular control over rendering, leading to enhanced user experiences and improved application responsiveness.

React Server Components represent a significant shift in React architecture, leveraging both server and client environments to optimize efficiency and interactivity. Offering a powerful framework for building modern web applications that excel in efficiency, speed, and interactivity. As we delve deeper into this groundbreaking technology, we uncover its potential to redefine the landscape of web development and pave the way for a new generation of web experiences.

Client Components

Client components represent the familiar React components that we’ve been utilizing and discussing in previous rendering techniques. They are typically rendered on the client side, enabling users to interact with them directly within the browser environment. However, they can also be rendered to HTML once on the server, providing users with immediate access to the page’s HTML content instead of encountering a blank screen.

While the concept of rendering client components on the server may initially appear perplexing, it’s beneficial to perceive them as components primarily designed for client-side execution, yet capable of server-side rendering as an optimization strategy. Client components leverage the client environment, including the browser, enabling them to utilize state, effects, and event listeners to manage interactivity. Moreover, they can access browser-exclusive APIs, such as geolocation or local storage, facilitating the creation of user interfaces tailored to specific use cases.

The term “client component” doesn’t introduce any novel concepts; rather, it serves to distinguish these components from the newly introduced server components, providing clarity in architecture and development practices.

Server Components

Server components represent a groundbreaking addition to React architecture, specifically engineered to function exclusively on the server. Unlike client components, the code for server components remains on the server and is never downloaded to the client.

This design decision offers numerous advantages for React applications, including enhanced security, improved performance, and streamlined development workflows. By executing exclusively on the server, server components alleviate the burden on client devices, resulting in faster loading times and reduced client-side processing requirements. Additionally, server components facilitate efficient server-side rendering, enabling the generation of HTML content directly on the server, thereby optimizing SEO and enhancing initial page load speed.

Benefits of Server Components

  1. Reduced Bundle Sizes:
    Server components minimize bundle sizes by keeping code on the server, eliminating the need to send large dependencies to the client. This benefits users with slower internet connections or less capable devices, as it reduces the download, parsing, and execution of JavaScript. Additionally, it streamlines app loading and interaction by removing the hydration step.

  2. Direct Access to Server-Side Resources:
    Server components enable efficient data fetching and rendering by accessing server-side resources like databases or file systems. Leveraging the server’s computational power and proximity to data sources, they manage compute-intensive rendering tasks and send only interactive code to the client.

  3. Enhanced Security:
    Server components enhance security by executing exclusively on the server, keeping sensitive data and logic, such as tokens and API keys, away from the client side.

  4. Improved Data Fetching Efficiency:
    Server components optimize data fetching by shifting sequential round trips from client to server, reducing request latency and improving overall performance. This eliminates client-side waterfalls and improves application responsiveness.

  5. Caching:
    Server-side rendering enables caching of results, which can be reused across subsequent requests and different users. This approach significantly improves performance and reduces costs by minimizing the amount of rendering and data fetching required.

  6. Faster Initial Page Load and First Contentful Paint:
    Server components accelerate the initial page load and first contentful paint by generating HTML on the server, making pages immediately visible to users without the delay of downloading, parsing, and executing JavaScript.

  7. Improved SEO:
    Server-rendered content is fully accessible to search engine bots, enhancing the indexability of pages and improving search engine optimization.

  8. Efficient Streaming:
    Server components allow rendering to be divided into manageable chunks, which are streamed to the client as they become available. This enables users to start viewing parts of the page earlier, eliminating the need to wait for the entire page to finish rendering on the server.

With the RSC architecture:

server components handle data fetching and static rendering, while client components manage interactive elements. This architecture enables React applications to leverage the strengths of both server and client rendering, using a unified language, framework, and set of APIs. React Server Components represent a significant advancement over traditional rendering techniques, addressing their limitations and delivering superior performance and user experiences.

Key takeaways of RSC

  1. Component Separation:
    React Server Components (RSC) introduce a novel approach by categorizing components into two distinct types: server components and client components, effectively separating rendering responsibilities.

  2. Server Components:
    Server components operate exclusively on the server, where they access data and prepare content without being sent to the browser. This architecture accelerates app performance for users, as less information needs to be downloaded to the client. However, server components cannot directly manage clicks or interactivity.

  3. Client Components:
    In contrast, client components execute within the user’s browser and handle interactive elements such as clicking and typing. While they primarily operate client-side, client components can also be rendered on the server to facilitate a fast initial load of the site.

  4. Performance and Security:
    By leveraging a combination of server and client components, RSC architectures enhance website performance, security, and usability. By offloading non-interactive rendering tasks to the server and focusing client components on interactivity, websites become faster, more secure, and more accessible across different devices and network conditions.

In summary, React Server Components revolutionize the way React applications are built, offering a streamlined approach that optimizes performance, security, and user experience. By intelligently separating rendering responsibilities between server and client components, RSC architectures pave the way for faster, more efficient, and more resilient web applications.

Closing Thoughts:

This deep dive into the evaluation of rendering techniques serves as a crucial foundation for understanding rendering in Next.js. The connection between the two is straightforward: the app router in Next.js is intricately built around the principles of React Server Components (RSC) architecture.

It’s worth noting that all the features and benefits we’ve discussed — such as improved performance, enhanced security, and streamlined data fetching — are already baked into the latest version of Next.js. By comprehensively understanding the evolution of React’s rendering strategies, you now possess the necessary background to grasp Next.js rendering intricacies with clarity and confidence.

In forthcoming articles, we’ll delve into practical implementations and code examples to illustrate how these concepts manifest in real-world scenarios. In forthcoming articles, we’ll explore Next.js rendering in depth, providing practical insights and techniques to enrich your development journey. Stay tuned for a deeper dive into practical implementations!

Until then, happy coding!

Top comments (4)

Collapse
 
leandro_nnz profile image
Leandro Nuñez

Great article. Thanks for sharing!

Collapse
 
federicofanini0 profile image
Federico Fan ☄️

Hi Shane, great article! I’m currently learning React and NextJS. Do you have any learning tips?

Collapse
 
shanedushyantha profile image
ShaneDushyantha

Hi Fed,

*1st *
You gotta master JavaScript "keep the basics close to you"
JS Tutorial Playlist

2nd
Go through React
React Tutorial Playlist

3rd
Next JS
Next 14 Tutorial Playlist

If you walk through the above 3 tutorial playlists, you'll become good at what you learned.
** Anyway these are my favourite Tutorials

Collapse
 
federicofanini0 profile image
Federico Fan ☄️

Thanks Shane, really appreciate! I’ll going through these tutorials 🙏🏻