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:
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.
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.
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.
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
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.
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.
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.
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.
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.
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."
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.
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:
Eseguire la logica di accesso ai dati e la renderizzazione iniziale sul server
Mantenere l'interattività sul client dove necessario
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
);
}
Vantaggi tecnici delle Server Actions
Semplificazione delle API endpoint: Le Server Actions eliminano la necessità di creare endpoint API per modificare i dati. Permettono di scrivere funzioni asincrone che vengono eseguite sul server e possono essere invocate direttamente dai componenti Client o Server.
Integrazione nativa con i form: Passando una Server Function all'attributo action di un form, React può migliorare progressivamente il form, permettendo l'invio anche prima del caricamento completo del bundle JavaScript.
Tipizzazione end-to-end: A differenza degli endpoint API tradizionali, le Server Actions sono intrinsecamente type-safe, garantendo maggiore coerenza tra client e server.
Gestione dello stato dell'interfaccia: Con i nuovi hook come useActionState e useFormStatus, le Server Actions offrono meccanismi integrati per la gestione degli stati di caricamento e degli errori durante le operazioni di form.
Queste caratteristiche rendono le Server Actions particolarmente adatte per applicazioni che richiedono una forte integrazione tra client e server, mantenendo al contempo una chiara separazione delle responsabilità.
Conclusioni
React Server Components e Server Actions rappresentano un cambiamento paradigmatico nello sviluppo di applicazioni React, promuovendo una separazione più netta tra logica server e client. Queste funzionalità, ora stabilizzate in React 19, offrono vantaggi significativi in termini di prestazioni, sicurezza e complessità del codice.
L'adozione di queste tecnologie richiede un ripensamento dell'architettura delle applicazioni, ma offre benefici sostanziali che giustificano l'investimento, specialmente per applicazioni con requisiti di prestazioni e SEO elevati.