Diego Betto's Blog
React 19 - Server Components - Foto di Jan Antonin Kolar su Unsplash

Diego Betto · 10 settembre 2026 · 8 min di lettura

React Server Components: cosa sono e come cambiano il modo di scrivere componenti

Server Components vs Client Components, le direttive 'use client' e 'use server', cosa gira dove, e come si integrano con Suspense e use().

Condividi:XLinkedInFacebookWhatsApp

I React Server Components (RSC) sono, secondo molti pareri, il cambiamento architetturale più grande che React abbia avuto dall’arrivo degli hook. Cambiano un’assunzione di base con cui la maggior parte degli sviluppatori React è cresciuta: che ogni componente, prima o poi, gira nel browser. Con i Server Components, alcuni componenti non arrivano mai al client — girano una sola volta, sul server, e mandano giù il risultato già renderizzato come parte della risposta.

ℹ️ Nota

I Server Components hanno bisogno di un framework o di un’integrazione a livello di bundler per funzionare (Next.js App Router, Waku, Parcel RSC, o un setup personalizzato con react-server-dom-webpack/react-server-dom-parcel). Vite + React da soli non offrono un runtime RSC pronto all’uso — questo articolo spiega il modello concettuale, valido a prescindere da quale di questi strumenti usi.

Due tipi di componenti, un solo albero

In un’app con RSC abilitati, ogni componente è o un Server Component o un Client Component, e li mescoli entrambi nello stesso albero.

  • I Server Component girano solo sul server (o a build time). Non si ri-renderizzano mai nel browser e non spediscono JavaScript per la propria logica.
  • I Client Component sono quello che React ha sempre fatto: girano nel browser, possono usare stato, effect ed event handler, e si idratano normalmente.

Di default, ogni componente in un’app con RSC è un Server Component, a meno che tu non scelga esplicitamente il contrario.

La direttiva 'use client'

Per marcare un componente (e tutto ciò che importa) come Client Component, aggiungi la direttiva 'use client' in cima al file:

"use client";

import { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>Cliccato {count} volte</button>;
}

Questo è un confine, non un interruttore riga per riga: una volta che un file ha 'use client', quel componente e tutto ciò che renderizza diventa parte del bundle client. Metti questa direttiva sulle foglie della tua UI interattiva, non in cima all’app — più il tuo albero resta server-only, meno JavaScript arriva al browser.

Cosa possono e non possono fare i Server Components

I Server Components possono:

  • leggere file, interrogare un database o chiamare API interne direttamente, con async/await, senza bisogno di un fetch lato client;
  • importare pacchetti server-only (un ORM, una libreria per il filesystem) senza preoccuparsi della dimensione del bundle, perché quel codice non arriva mai al browser;
  • renderizzare Client Component, passando loro prop serializzabili.

I Server Components non possono:

  • usare useState, useEffect, useContext, o qualsiasi hook che dipenda dal fatto che il componente si ri-renderizzi nel browser — non c’è un runtime browser a cui agganciarsi;
  • collegare event handler (onClick, onChange, …) direttamente — la gestione degli eventi richiede JavaScript in esecuzione lato client;
  • usare API disponibili solo nel browser (window, localStorage, document).
// Server Component — nessuna direttiva necessaria, è il default
async function ProductPage({ id }) {
  // accesso diretto al database, nessuna API route, nessun fetch client
  const product = await db.products.findById(id);

  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      {/* La parte interattiva è un Client Component separato */}
      <AddToCartButton productId={product.id} />
    </div>
  );
}
"use client";

// Client Component — richiede interattività, quindi opta esplicitamente
export default function AddToCartButton({ productId }) {
  const [added, setAdded] = useState(false);
  return (
    <button onClick={() => setAdded(true)}>{added ? "Aggiunto!" : "Aggiungi al carrello"}</button>
  );
}

⚠ Le prop devono essere serializzabili

Le prop passate da un Server Component a un Client Component vengono serializzate e inviate sulla rete come parte del payload RSC — pensa a JSON, più qualche extra specifico di React come promise e JSX. Non puoi passare una funzione (come una callback definita sul server), un’istanza di classe, o qualsiasi cosa non serializzabile come prop verso un Client Component.

Perché conta: dimensione del bundle e accesso ai dati

Due vantaggi concreti spingono l’adozione degli RSC:

  • Bundle client più piccoli. Ogni dipendenza usata solo dentro un Server Component — un parser Markdown, una libreria pesante per le date, un ORM — non arriva mai al browser. Il tuo bundle JavaScript contiene solo ciò che è effettivamente interattivo.
  • Accesso diretto al backend, senza layer API. Un Server Component può parlare direttamente con il tuo database o filesystem. Non devi costruire e mantenere un endpoint REST/GraphQL solo per alimentare un componente che, comunque, sarebbe girato lato server.

È un trade-off diverso rispetto al React Compiler, che tratto in un articolo separato: il Compiler ottimizza i re-render che già avvengono lato client, mentre i Server Components rimuovono del tutto il rendering client-side (e il suo costo in JavaScript) per le parti di UI che non hanno bisogno di interattività.

Streaming di dati verso i Client Component con use()

I Server Components possono avviare un’operazione asincrona e passare la promise non ancora risolta giù a un Client Component, che poi la srotola con use() — l’API che tratto nel dettaglio nell’articolo use vs useContext.

// Server Component
function Page({ id }) {
  const commentsPromise = db.comments.findByPostId(id); // non awaited qui

  return (
    <div>
      <PostContent id={id} />
      <Suspense fallback={<p>Caricamento commenti...</p>}>
        <Comments commentsPromise={commentsPromise} />
      </Suspense>
    </div>
  );
}
"use client";

function Comments({ commentsPromise }) {
  const comments = use(commentsPromise); // risolta lato client, dentro Suspense
  return (
    <ul>
      {comments.map((c) => (
        <li key={c.id}>{c.text}</li>
      ))}
    </ul>
  );
}

La pagina non aspetta che i commenti siano caricati prima di inviare il resto dell’HTML — arrivano in streaming appena pronti, mentre PostContent viene renderizzato subito. Questo è il pattern che lega insieme Server Components, Suspense e use() in un unico modello coerente di data fetching.

'use server': l’altra direttiva

Mentre 'use client' marca i confini lato client, 'use server' fa l’opposto: marca una funzione come Server Action, chiamabile da un Client Component ma garantita ad eseguire sul server (submit di form, mutazioni).

"use server";

export async function likePost(postId) {
  await db.posts.incrementLikes(postId);
}
"use client";

import { likePost } from "./actions";

export default function LikeButton({ postId }) {
  return <button onClick={() => likePost(postId)}>Mi piace</button>;
}

Questo argomento meriterebbe un articolo dedicato — collegarlo a useActionState e useFormStatus, che ho già trattato, chiude il cerchio tra submit del form e mutazione server. Per ora, la cosa importante è sapere che le due direttive sono complementari: 'use client' fa entrare un componente nel browser, 'use server' fa entrare una funzione nel server, chiamabile da qualsiasi punto.

Server Components vs Client Components a colpo d’occhio

Server Component Client Component
Gira nel browser No
Spedisce JavaScript No
Può usare useState/useEffect No
Può collegare event handler No
Accesso diretto a database/filesystem No
Richiede una direttiva No (default) Sì, 'use client'

FAQ

❓ Mi serve Next.js per usare i Server Components?

No, ma ti serve un framework o un bundler che implementi il protocollo RSC — Next.js App Router è la scelta più comune oggi, ma anche Waku e Parcel RSC lo supportano. React fornisce le primitive; il bundling e il collegamento server/client sono compito del framework.

❓ Un Server Component può importare un Client Component, e viceversa?

Un Server Component può renderizzare un Client Component, passando giù prop serializzabili. Un Client Component non può importare direttamente un Server Component — invece, un Server Component può passarne uno come children, così il Client Component deve solo renderizzare {children} senza sapere cosa contiene.

❓ I Server Components sono la stessa cosa del server-side rendering (SSR)?

No. L’SSR renderizza in HTML sul server i tuoi client component esistenti per il primo caricamento, ma poi si idratano comunque e spediscono JavaScript completo al browser. I Server Components non spediscono mai il proprio JavaScript — sono un concetto diverso, più granulare, che si compone con l’SSR invece di sostituirlo.

Conclusione

I Server Components dividono il tuo albero di componenti in una parte che gira una sola volta sul server e una parte che gira nel browser come React ha sempre fatto, con 'use client' e 'use server' a marcare i confini tra le due. Il vantaggio è un bundle client più piccolo e accesso diretto al backend senza un layer API dedicato — ma richiede un framework che implementi il protocollo RSC, e cambia alcune abitudini (niente hook, niente event handler, solo prop serializzabili) che vale la pena interiorizzare prima di affidarcisi in un progetto reale.

Riferimenti

Condividi:XLinkedInFacebookWhatsApp
Diego Betto

Scritto da

Diego Betto

Co-Fondatore & CTO presso PAPION. Senior full-stack engineer specializzato in React, TypeScript, Node.js e sicurezza applicativa.