IMAD EDDINE NOUALI

BY IMAD EDDINE NOUALI  - JAN 22, 2025

Next.js Server and Client Components


Overview

Next.js has revolutionized how we build modern web applications by introducing a hybrid rendering approach that leverages both server and client components. This article dives into the rarely discussed mechanics of how Next.js processes page requests and renders components behind the scenes.

The Journey of a Page Request in Next.js

When a user navigates to a page in a Next.js application, a fascinating series of events unfolds. Understanding this process is crucial for optimizing your applications and leveraging Next.js features effectively.

Step-by-Step Execution

  1. Browser Sends a GET Request
    • When you visit a URL like http://localhost:3000, your browser sends a GET request to the Next.js server.
  2. Next.js Server Renders Server & Client Components
    • Server components (default in Next.js) are executed entirely on the server. They can fetch data, read from the filesystem, and return static or dynamic HTML.
    • Client components (marked with "use client") are also rendered once on the server to generate the initial HTML structure.
    • The server sends back a minimum HTML structure, including a script tag referencing the client JavaScript bundle.
  3. Browser Parses HTML & Loads JS
    • The browser receives and parses the HTML, identifies the script tags, and sends another request to fetch the client-side JS bundle.
  4. Client-Side Hydration
    • The downloaded JS is executed in the browser, turning static HTML into a fully interactive React app.
    • This is when event handlers, hooks, and other browser-only features come to life.

One of the most misunderstood aspects of Next.js is that both server and client components initially render on the server. When the request hits the server:

  1. The server component tree is processed first.
  2. Client components are also pre-rendered on the server to generate their initial HTML.
  3. The complete HTML structure is assembled, containing the rendered output of both component types.
Server Component Example
 
export default function ProductPage({ params }) {
  // This component runs exclusively on the server
  const product = fetchProductData(params.id); // Server-side data fetching
  
  return (
    <div>
      <h1>{product.name}</h1>
      <ProductDetails product={product} />
      <AddToCartButton productId={product.id} /> {/* Client Component */}
    </div>
  );
}
                    
Client Component Example
 
"use client"; // This directive marks it as a client component
import { useState } from 'react';

export default function AddToCartButton({ productId }) {
  const [isAdded, setIsAdded] = useState(false);
  
  return (
    <button 
      onClick={() => {
        addToCart(productId);
        setIsAdded(true);
      }}
    >
      {isAdded ? 'Added to Cart' : 'Add to Cart'}
    </button>
  );
}
                    

Key Insights About Next.js Components

The Dual Nature of Client Components

A surprising fact for many developers: client components in Next.js have a dual life. They:

  • First render on the server to generate initial HTML
  • Then run again on the client for interactivity

This approach combines the benefits of immediate content visibility with rich interactivity after JavaScript loads.

Server Components Are Pure Rendering Engines

Server components never run in the browser. They:

  • Can directly access backend resources
  • Don't include any JavaScript for the client to download
  • Can't use hooks, browser APIs, or event handlers
  • Produce pure HTML that becomes part of the initial page load

Optimizing the Component Split

Understanding this rendering process helps make smarter decisions about which parts of your application should be server components versus client components

 
// Good Pattern: Keep as much as possible in server components
// and use client components only where interactivity is needed

// Server Component
export default function Dashboard() {
  const userData = fetchUserData(); // Server-side operation
  const recommendations = getRecommendations(); // Server-side operation
  
  return (
    <main>
      <UserProfile data={userData} /> {/* Server Component */}
      <RecommendationList items={recommendations} /> {/* Server Component */}
      <InteractiveFilter /> {/* Client Component - only this sends JS to client */}
    </main>
  );
}
                    

Performance Implications

This hybrid rendering approach has significant performance advantages:

  • Faster Initial Page Load: Users see content before all JavaScript has loaded
  • Reduced Client-Side JavaScript: Server components don't ship any JS to the client
  • Progressive Enhancement: The page is usable even before becoming fully interactive

Practical Example: Building a Product Page

Let's see how we might structure a product page using this knowledge:

 
import ProductGallery from './ProductGallery';
import ProductSpecs from './ProductSpecs';
import AddToCartSection from './AddToCartSection';
import RelatedProducts from './RelatedProducts';

export default async function ProductPage({ params }) {
  // Server-side data fetching
  const product = await fetchProductById(params.id);
  const specs = await fetchProductSpecs(params.id);
  const related = await fetchRelatedProducts(product.category);
  
  return (
    <div className="product-page">
      <h1>{product.name}</h1>
      <div className="product-main">
        <ProductGallery images={product.images} /> {/* Server Component */}
        <div className="product-info">
          <p className="price">price</p>
          <ProductSpecs data={specs} /> {/* Server Component */}
          <AddToCartSection product={product} /> {/* Client Component */}
        </div>
      </div>
      <RelatedProducts products={related} /> {/* Server Component */}
    </div>
  );
}
                    

Conclusion

Next.js brilliantly combines the worlds of server-side rendering and client-side interactivity. By understanding that all components—both server and client—initially render on the server, developers can make more informed decisions about component architecture.

This hybrid approach delivers the best of both worlds: the immediate content visibility and SEO benefits of server rendering, paired with the rich interactivity of client-side React. As you build your Next.js applications, use this knowledge to optimize your component structure, minimizing client JavaScript while maximizing user experience.

Remember these key takeaways:

  • Both server and client components render on the server first
  • Client components "hydrate" on the browser after JavaScript loads
  • Keep as much as possible in server components
  • Use client components only where interactivity is needed

By applying these principles, you‘ll create Next.js applications that are both performant and feature-rich.


YOU MIGHT ALSO LIKE...