React Server Components e Server Actions: La Rivoluzione di React 19 per Applicazioni Moderne

Pubblicato: 5 mag 2025
Di: Lorenzo Galassi

React 19 stabilizza ufficialmente funzionalità che erano precedentemente in fase sperimentale: React Server Components (RSC) e Server Actions. Questi componenti dell'architettura React rappresentano un significativo cambio di paradigma nello sviluppo di applicazioni React, consentendo una chiara separazione tra codice eseguito sul server e codice eseguito sul client. Sebbene queste tecnologie siano già state implementate in framework come Next.js, React 19 le incorpora ufficialmente nella libreria core, stabilizzandone le API pubbliche ma mantenendo in evoluzione le implementazioni sottostanti per i framework e i bundler.

L'evoluzione del rendering in React

Prima di approfondire i RSC, è importante esaminare l'evoluzione delle strategie di rendering in React:

  1. Client-Side Rendering (CSR): L'approccio originale di React, dove il server invia un HTML minimo e il client esegue tutto il rendering, richiedendo significativa esecuzione di JavaScript sul client.

  2. Server-Side Rendering (SSR): Introdotto per migliorare l'esperienza utente iniziale, spostando il rendering dal client al server. Invece di inviare un documento HTML vuoto, il server renderizza l'HTML iniziale e lo invia al browser, riducendo il tempo di visualizzazione del contenuto.

  3. Static Site Generation (SSG): Questo approccio compila e costruisce l'intera applicazione in fase di build, generando file statici (HTML e CSS) che vengono poi ospitati su CDN. È particolarmente adatto per progetti nei quali il contenuto non cambia frequentemente.

  4. Incremental Static Regeneration (ISR): Un'evoluzione dell'SSG che si posiziona tra SSG e SSR tradizionale, consentendo la rigenerazione di singole pagine in risposta a una richiesta del browser, senza necessità di ricostruire l'intero sito.

I React Server Components rappresentano il passo successivo in questa evoluzione, consentendo agli sviluppatori di renderizzare alcuni componenti interamente sul server, minimizzando l'impronta JavaScript sul client e offrendo un approccio più granulare alla renderizzazione.

React Server Components: architettura e funzionamento

I React Server Components sono componenti eseguiti esclusivamente sul server, con diversi vantaggi tecnici rispetto alle precedenti strategie di rendering:

// ProductsPage.tsx
// Server Component (impostazione predefinita in React 19)
import { db } from '../database';
import ProductCard from './ProductCard';

interface Product {
  id: string;
  name: string;
  description: string;
  price: number;
}

export default async function ProductsPage(): Promise {
  // Accesso diretto al database o altre risorse server-only
  const products: Product[] = await db.products.findMany();
  
  return (
    

Catalogo Prodotti

{products.map(product => ( ))}
); }

Caratteristiche tecniche principali

  1. Esecuzione server-only: I RSC vengono eseguiti esclusivamente sul server, sia durante la build che durante le richieste. Possono operare una sola volta in fase di build sul server CI, oppure per ogni richiesta utilizzando un web server.

  2. Riduzione del JavaScript client: I bundle JavaScript lato client risultano significativamente più piccoli poiché i Server Components non vengono inclusi nel bundle client. Tuttavia, è importante precisare che non è completamente "zero JavaScript" in quanto i componenti client che interagiscono con i Server Components richiedono ancora codice JavaScript.

  3. Accesso diretto alle risorse server: I componenti hanno accesso a tutte le risorse backend (database, filesystem, server, ecc.), consentendo di inviare query direttamente dai componenti, eliminando la necessità di chiamate API intermedie.

  4. Gestione integrata dei dati: Poiché i componenti sono renderizzati sul server, possono interrogare direttamente il database. Questo sposta il caricamento dei dati sul server, riducendo significativamente la latenza rispetto al recupero dei dati dal client.

  5. Limitazioni d'uso: I Server Components non hanno accesso a gestori di eventi lato client, stato ed effetti. Questo significa che non è possibile utilizzare gestori di eventi o hook React come useState, useReducer e useEffect.

Differenze con SSR tradizionale

A differenza del Server-Side Rendering tradizionale, i React Server Components:

Eliminano il processo di idratazione per i componenti server: I Server Components non eseguono JavaScript sul lato client e quindi non richiedono idratazione come nel SSR, dove l'intera pagina deve essere idratata per diventare interattiva.

  1. Ottimizzano la dimensione del bundle JavaScript: Come indicato nell'RFC ufficiale: "I Server Components vengono eseguiti solo sul server e hanno un impatto nullo sulla dimensione del bundle. Il loro codice non viene mai scaricato dai client, contribuendo a ridurre le dimensioni dei bundle e migliorare i tempi di avvio."

  2. Consentono l'accesso diretto alle risorse server: I Server Components offrono l'accesso diretto alle risorse server dall'interno dei componenti, mentre con SSR tradizionale questo è generalmente limitato ai livelli superiori della pagina.

  3. Offrono una granularità a livello di componente: A differenza dell'SSR, che avviene solo una volta durante il caricamento iniziale della pagina, i Server Components possono essere ricaricati individualmente dal server e incorporati nell'albero dei componenti esistente senza perdere lo stato client.

Questa architettura mista consente una separazione più netta tra la logica di rendering server e l'interattività client, permettendo alle applicazioni di combinare i vantaggi di entrambi gli approcci.

Composizione di Client e Server Components

L'architettura React 19 consente la composizione tra componenti server e client:

// ServerComponent.tsx
// Server Component (default)
import ClientComponent from './ClientComponent';

export default function ServerComponent(): React.ReactElement {
  
  const serverData = fetchDataFromDatabase();
  
  return (
    

Dati dal server: {serverData}

{/* I componenti client possono essere annidati nei componenti server */}
); } // ClientComponent.tsx 'use client'; import { useState } from 'react'; interface ClientComponentProps { initialData: string; } export default function ClientComponent({ initialData }: ClientComponentProps): React.ReactElement { const [data, setData] = useState(initialData); return (

Dati sul client: {data}

); }

Questo pattern consente di:

  1. Eseguire la logica di accesso ai dati e la renderizzazione iniziale sul server

  2. Mantenere l'interattività sul client dove necessario

  3. Ottimizzare la dimensione del bundle inviando JavaScript solo per i componenti client

Server Actions: funzioni server chiamabili dal client

Le Server Actions complementano i Server Components, fornendo un meccanismo per eseguire codice sul server in risposta ad eventi sul client:

// actions.ts
'use server';

import { z } from 'zod';
import { db } from '../database';



const ProductSchema = z.object({
  // schema definition
});

type ProductData = z.infer;

export async function createProduct(formData: FormData): Promise<{ 
  success: boolean; 
  error?: string | Record;
}> {
  try {

    const rawData = {
      name: formData.get('name'),
      price: formData.get('price'),
      description: formData.get('description') || '',
    };

    // Validazione con Zod
    const validationResult = ProductSchema.safeParse(rawData);
    
    // Se la validazione fallisce, restituisci gli errori
    if (!validationResult.success) {
      // Formatta gli errori di Zod
      const formattedErrors = validationResult.error.format();
      return { 
        success: false, 
        error: formattedErrors
      };
    }
    
    // Ottieni i dati validati
    const data = validationResult.data;
    
    // Accesso diretto al database
    await db.products.create({
      data
    });
    
    return { success: true };
  } catch (error) {
    console.error('Errore durante la creazione del prodotto:', error);
    return { 
      success: false, 
      error: 'Si è verificato un errore durante la creazione del prodotto' 
    };
  }
}

Implementazione nel Client Component

// ProductForm.tsx
'use client';

import { useActionState } from 'react';
import { createProduct, ActionResponse } from './actions';
import { useState } from 'react';

// Stato iniziale
const initialState: ActionResponse | null = null;

export default function ProductForm(): React.ReactElement {
  
  const [fieldErrors, setFieldErrors] = useState>({});
  
  // useActionState per gestire l'intera azione
  const [state, formAction, isPending] = useActionState(
    async (previousState, formData) => {
      try {
        
        const result = await createProduct(formData);
        
        if (!result.success && result.errors) {
          setFieldErrors(result.errors);
        } else {
          setFieldErrors({});
          if (result.success) {
            setTimeout(() => {
              document.querySelector('form')?.reset();
            }, 0);
          }
        }
        return result;
      } catch (error) {
        return { 
          success: false, 
          message: 'Si è verificato un errore durante la richiesta'
        };
      }
    },
    initialState
  );
  
  return (
    

Aggiungi nuovo prodotto

{fieldErrors.name && (
{fieldErrors.name.map((error, i) => (

{error}

))}
)}
{fieldErrors.price && (
{fieldErrors.price.map((error, i) => (

{error}

))}
)}