Rendering in Next.js - server and client components in a nutshell

Blog image - Renderowanie w Next.js - server i client components w pigułce

16/12/2023

8 min

Bartosz Lewandowski

Rendering in Next.js - server and client components in a nutshell

16/12/2023

8 min

Bartosz Lewandowski

Blog image - Renderowanie w Next.js - server i client components w pigułce

Table of Contents

  1. Rendering - what's it all about?
  2. Server Components
  3. Client Components
  4. Sample application structure
  5. Suspense and Streaming
  6. Summary

Thanks to the post Routing in Next.js - what it is and how to create routes you've already uncovered the secrets of routing in Next.js, but are you ready for the next level? Prepare for an exciting journey through the world of rendering in Next.js! Discover the magic of Server Components, play with the possibilities of Client Components, and find out how Suspense and Streaming are revolutionizing the performance and interactivity of your application. Are you ready to bring your application to life like never before? Read on and dive into new dimensions of Next.js!

Rendering - what's it all about?

Rendering in the context of web applications refers to the process of transforming the code you write into user interfaces. Next.js allows you to create hybrid web applications where some of the code can be rendered on the server and some on the client. This section will explain the differences between these rendering environments, strategies, and runtimes. The client refers to the browser on the user's device, which sends a request to the server for the application code and then transforms the server's response into a user interface. The server is the computer that stores the application code, receives requests from the client, and sends back the appropriate response.

The lifecycle of requests and responses in web applications

In every web application, regardless of its type, there is a certain standard cycle of actions that can be described in several steps:

  1. User interaction: It all starts with a user action. This could be, for example, clicking a link, filling out and submitting a form, or entering a website address directly in the browser's address bar.
  2. Sending an HTTP request: In response to the user's action, the browser (client) sends a request to the server. This request contains information about what the user needs - it could be specific pages, images, scripts, etc., as well as information about the request method (e.g., GET or POST) and any additional data.
  3. Processing the request by the server: When the server receives the request, it begins processing it. This can involve various actions, such as routing the request to the appropriate location, retrieving data from a database, or performing server-side computations.
  4. Server response: After processing the request, the server responds by sending back an HTTP response to the client. This response contains a status code indicating whether the request was successfully processed, as well as the requested resources, such as HTML, CSS, JavaScript, or other files.
  5. Processing the response by the client: The client, usually a web browser, receives the response and renders the user interface based on it - meaning it transforms the received data into visible elements on the page.
  6. Further user interaction: Once the page is rendered, the user can interact with it further, potentially leading to more requests and responses, and so the cycle repeats.

When building hybrid web applications, a crucial aspect is deciding how to divide the work between the server and the client in this cycle and where exactly to set the boundary between these two environments. This boundary determines which parts of the application are processed on the server side and which on the client side.

Server Components

Server Components in Next.js is a technology that allows creating user interfaces rendered on the server. In Next.js, the rendering process is divided according to route segments, enabling data streaming and partial rendering. There are three main server-side rendering strategies:

  1. Static rendering: This method involves rendering routes during the build of the application or in the background after data updates. The result can be distributed via a content delivery network (CDN), allowing the sharing of rendering results between different users and server requests.
  2. Dynamic rendering: In this strategy, routes are rendered individually for each user at the time of the request.
  3. Streaming: Server Components allow the rendering work to be divided into parts, which are then streamed to the client. This enables the user to see parts of the page faster before the entire content is rendered on the server.

Benefits of server-side rendering

  1. Data Fetching: Server Components allow moving the data fetching process to the server, which can reduce the time needed to obtain the data required for rendering and decrease the number of requests sent by the client.
  2. Security: With Server Components, sensitive data and logic, such as tokens and API keys, can be kept on the server without the risk of exposing them to clients.
  3. Reducing bundle size: Server Components allow keeping large dependencies that previously affected the JavaScript bundle size on the client side, on the server.
  4. Initial Page Load and First Contentful Paint (FCP): HTML can be generated on the server, allowing the user to quickly preview the page without waiting for JavaScript to be downloaded, parsed, and executed.
  5. Server Components enable convenient and intuitive data fetching from various sources. They can be used as asynchronous functions with await, allowing for waiting on data. Interestingly, these components allow direct connection to a database and fetching data directly within the component. Below is an example of the correct use of a Server Component that fetches data from a database:
async function ProductsList() {
	const response = await fetch("<http://example.com/api/products>");
	const products = await response.json();
	return (
		<ul>
 
			{products.map((product) => (
				<li key={product.id}>{product.name}</li>
			))}
 
		</ul>
	);
}

Client Components

Client Components in Next.js allow creating interactive user interfaces that are rendered on the client side at the time of the request. In Next.js, using client-side rendering (client-side rendering) is an opt-in option, meaning you must explicitly decide which React components should be rendered on the client side. In Client Components, we can use hooks such as useState, useEffect, useReducer, and additionally handle events like onClick. Below you will find information on how Client Components work, how they are rendered, and when you can use them.

Usage

To use Client Components, you can add "use client" at the beginning of the file, above the imports. "use client" is used to declare the boundary between Server and Client Components modules. This means that by defining "use client" in a file, all other modules imported into that file, including child components, are treated as part of the client bundle. Example:

import { useState } from "react";
 
export default function Counter() {
	const [count, setCount] = useState(0);
	return (
		<div>
			      <p>You clicked {count} times</p>      
			<button onClick={() => setCount(count + 1)}>Click me</button>    
		</div>
	);
}

Benefits of client-side rendering

Client-side rendering offers several benefits, including:

  • Interactivity: Client Components can use state, effects, and event listeners, meaning they can provide immediate feedback to the user and update the UI.
  • Browser APIs: Client Components have access to browser APIs, such as geolocation or localStorage, allowing for creating user interfaces tailored to specific use cases.

Sample application structure

Let's assume we're building an e-commerce application. Here's what the structure might look like:

Server Components:

Navigation: Navigation components, such as the main menu or links to specific sections, are ideal for server-side rendering. Product list: Rendering the product list, possibly using Server Side Rendering (SSR) for better performance and SEO. Product details: Like the product list, product details can be served from the server for fast loading.

Client Components:

Cart: Dynamic user interaction, real-time updates. Slider: Displaying a list of popular products with scrolling animation. Interactive UI elements: Such as sorting buttons, filters, animations.

Combining both types

Ideally, both technologies work together. For example, the product details page (Server Component) can load quickly from the server, while dynamic elements, such as user reviews or a form for adding opinions (Client Components), can be loaded and become interactive when the user scrolls to the appropriate section of the page. Remember that the choice between Server and Client Components should be dictated by the end user's needs and the specifics of a given part of the application, always with a focus on performance and user optimization.

Suspense and Streaming

The smoothness of loading web pages is a key element in providing excellent user experiences. Suspense, combined with Streaming and Loading UI techniques, opens new horizons in designing responsive and interactive user interfaces.

Loading UI

Using loading.ts allows creating intuitive and engaging loading interfaces. Instead of the traditional waiting for the entire content to load, users can immediately see that something is loading. This file can be placed in the main app directory or next to each page.tsx if you want to show, for example, a different loading component depending on the page.

Example:

export default function Loading() {
	return <div>Loading...</div>;
}

Using Suspense and Streaming

The introduction of new technologies, such as Streaming and Suspense in React 18, significantly impacts how web applications are loaded and rendered. Traditionally, this process involved fetching all data on the server, rendering the entire HTML, sending it to the client, and finally displaying it. These stages were performed sequentially, meaning the entire application had to wait until all its components were ready to be displayed.

The introduction of Streaming in React 18 significantly changes this process. With streaming, application loading can be divided into smaller, parallel steps. This allows for earlier display of parts of the application, even before all components are loaded. They no longer have to wait for the loading and rendering of other elements. This speeds up page loading time and improves the overall user experience.

A key element of using Streaming is the <Suspense> component. It allows delaying the rendering of parts of components until their content is loaded. This way, parts of the application can be displayed independently of the loading state of others, which is a huge convenience and improvement compared to previous methods.

To implement this technology, simply wrap the appropriate components with the <Suspense> tag. Below is an example of how you can modify the code to take advantage of Streaming and Suspense:

Example:

import { Suspense } from "react";
import { ProductImages, ProductDetails } from "./Components";
 
export default function Products() {
	return (
		<section>
			<Suspense fallback={<p>Loading product images...</p>}>     
				<ProductImages />    
			</Suspense>  
			<Suspense fallback={<p>Loading product details...</p>}>        
				<ProductDetails />     
			</Suspense
		</section>
	);
}

Summary

In summary, this post presented the key elements of rendering in Next.js, starting from the differences between server-side and client-side rendering, through a detailed discussion of Server Components and Client Components, and ending with Suspense and Streaming. The advantages and possibilities offered by each of these technologies were also highlighted, emphasizing their importance in creating efficient and modern web applications. We hope that our guide to rendering in Next.js was an exciting adventure for you. But we don't stop there! More fascinating topics await you.

We invite you to read our next article Data Fetching in Next.js - A Beginner's Guide. You will learn how to work efficiently with data, which is crucial in creating dynamic applications.

Frequently Asked Questions

  1. What is the difference between a Server Component and a Client Component in Next.js? Server Components are rendered on the server and are typically used for static parts of the application, such as navigation or initial data loading. Client Components are rendered on the client side and used for dynamic, interactive parts of the application, such as shopping carts, carousels, or animations.
  2. How do Suspense and Streaming impact the performance of an application in Next.js? Suspense and Streaming allow for smoother and more efficient application loading. They enable earlier display of parts of the page before all components are loaded, improving the user experience and reducing wait times.
  3. Can I use Server Components for direct database connections? Yes, Server Components allow direct use of async/await, which is convenient for fetching data directly within the component.
  4. What are the main benefits of using Server Components? The main benefits include better performance by reducing the size of bundles sent to the client, increased security by keeping sensitive data on the server, and more efficient data fetching.
  5. Do Client Components have access to browser APIs? Yes, Client Components have full access to browser APIs, allowing for creating advanced, interactive user interfaces tailored to specific use cases.
  6. How can I use 'use client' in Next.js? To use Client Components, you need to add the 'use client' declaration at the beginning of the file. This means that all components in that file will be treated as part of the client bundle and will be rendered on the client side.
  7. What are the best practices for combining Server Components and Client Components? The best practice is to use Server Components for parts of the application that do not require user interaction and can be rendered statically, such as navigation or product information. Client Components are best for dynamic and interactive elements, such as carousels, filters, or animations. It's important to find the right balance between both types of components, depending on the needs of the application and the user.
Bartosz Lewandowski

Bartosz Lewandowski

CEO

Newsletter