# React 19: Server Components in produzione - La guida completa > Implementare i Server Components di React 19 in produzione. Architettura, pattern, streaming, caching e ottimizzazioni per applicazioni ad alte prestazioni. - Published: 2026-01-08 - Updated: 2026-04-06 - Author: SharpSkill - Tags: react 19, server components, rsc, performance, next.js - Reading time: 14 min --- I Server Components rappresentano l'evoluzione più significativa in React dall'introduzione degli Hooks. Con React 19, questa architettura ha raggiunto la maturità ed è pronta per la produzione: i componenti possono essere eseguiti direttamente sul server, preservando al contempo l'interattività lato client. > **Prerequisiti** > > Questa guida presuppone familiarità con React e il Next.js App Router. Gli esempi utilizzano Next.js 14+, che implementa nativamente i React Server Components. ## Comprendere l'architettura dei Server Components I Server Components (RSC) introducono un nuovo paradigma: alcuni componenti vengono eseguiti esclusivamente sul server, altri sul client, ed entrambi possono coesistere nello stesso albero dei componenti. Questa separazione ottimizza drasticamente le prestazioni, riducendo il JavaScript inviato al browser. L'idea fondamentale si basa sul fatto che molti componenti non necessitano di interattività. Un componente che visualizza un elenco di articoli da un database, ad esempio, può funzionare interamente lato server. Solo gli elementi interattivi (pulsanti, form, animazioni) richiedono JavaScript lato client. ```tsx // app/articles/page.tsx // This component runs only on the server // No JavaScript is sent to the client for this component import { getArticles } from '@/lib/articles' import ArticleCard from './ArticleCard' import LikeButton from './LikeButton' // async/await directly in the component // Only possible with Server Components export default async function ArticlesPage() { // Direct database call (no REST API needed) const articles = await getArticles() return (

Recent Articles

{articles.map((article) => ( // ArticleCard is also a Server Component {/* LikeButton is a Client Component (interactive) */} ))}
) } ``` La direttiva `"use client"` contrassegna esplicitamente i componenti che necessitano di JavaScript nel browser. ```tsx // app/articles/LikeButton.tsx 'use client' // useState and interactive hooks require "use client" import { useState, useTransition } from 'react' import { likeArticle } from '@/actions/articles' interface LikeButtonProps { articleId: string initialLikes?: number } export default function LikeButton({ articleId, initialLikes = 0 }: LikeButtonProps) { // Local state for optimistic UI const [likes, setLikes] = useState(initialLikes) const [isPending, startTransition] = useTransition() const handleLike = () => { // Immediate optimistic update setLikes((prev) => prev + 1) // Server Action to persist startTransition(async () => { await likeArticle(articleId) }) } return ( ) } ``` Questa architettura riduce significativamente il bundle JavaScript: solo il codice di `LikeButton` viene inviato al client, non quello di `ArticlesPage` o `ArticleCard`. ## Pattern di composizione Server/Client La composizione tra Server e Client Components segue regole precise. Un Server Component può importare e renderizzare Client Components, ma il contrario non è direttamente possibile. Per passare contenuto server a un componente client, il pattern `children` offre la soluzione. ```tsx // components/InteractiveWrapper.tsx 'use client' import { useState, ReactNode } from 'react' interface InteractiveWrapperProps { children: ReactNode expandable?: boolean } // Client Component that wraps server content export function InteractiveWrapper({ children, expandable = false }: InteractiveWrapperProps) { const [isExpanded, setIsExpanded] = useState(!expandable) if (!expandable) { return
{children}
} return (
{isExpanded && (
{/* children can contain Server Components */} {children}
)}
) } ``` ```tsx // app/dashboard/page.tsx // Server Component using the client wrapper import { InteractiveWrapper } from '@/components/InteractiveWrapper' import { getStats, getRecentActivity } from '@/lib/dashboard' export default async function DashboardPage() { // Parallel server-side requests const [stats, activity] = await Promise.all([ getStats(), getRecentActivity() ]) return (
{/* Stats in an expandable wrapper */} {/* This content is rendered server-side then passed to client */}
{/* Recent activity */}
) } ``` Questo pattern consente di combinare l'interattività lato client con i dati renderizzati lato server, senza duplicare la logica. ## Recupero dati e caching > **API fetch estesa da React** > > React 19 estende automaticamente l'API nativa `fetch` aggiungendo deduplicazione e caching. Le richieste identiche all'interno dello stesso ciclo di rendering vengono eseguite una sola volta. Il recupero dei dati nei Server Components avviene direttamente con `async/await`. React gestisce automaticamente la deduplicazione delle richieste identiche. ```tsx // lib/api.ts // Centralized request configuration with caching const API_BASE = process.env.API_URL // Request with time-based revalidation export async function getProducts() { const response = await fetch(`${API_BASE}/products`, { // Revalidate every hour next: { revalidate: 3600 } }) if (!response.ok) { throw new Error('Failed to fetch products') } return response.json() } // Request without cache (real-time data) export async function getCurrentUser() { const response = await fetch(`${API_BASE}/me`, { // No cache, always fresh cache: 'no-store' }) if (!response.ok) { return null } return response.json() } // Request with tag for targeted invalidation export async function getProduct(id: string) { const response = await fetch(`${API_BASE}/products/${id}`, { next: { tags: [`product-${id}`], revalidate: 3600 } }) if (!response.ok) { throw new Error('Product not found') } return response.json() } ``` Per l'accesso diretto al database (Prisma, Drizzle), React Cache con `unstable_cache` offre le stesse funzionalità. ```tsx // lib/db-queries.ts import { unstable_cache } from 'next/cache' import { prisma } from '@/lib/prisma' // Cache categories (rarely modified) export const getCategories = unstable_cache( async () => { return prisma.category.findMany({ orderBy: { name: 'asc' } }) }, ['categories'], // Cache key { revalidate: 86400, // 24 hours tags: ['categories'] } ) // Cache products by category export const getProductsByCategory = unstable_cache( async (categoryId: string) => { return prisma.product.findMany({ where: { categoryId }, include: { images: true }, orderBy: { createdAt: 'desc' } }) }, ['products-by-category'], { revalidate: 3600, tags: ['products'] } ) // Cache invalidation after mutation export async function createProduct(data: ProductInput) { const product = await prisma.product.create({ data }) // Invalidate related caches revalidateTag('products') return product } ``` ## Streaming e Suspense per una UX ottimale Lo streaming consente l'invio progressivo di HTML al browser, visualizzando immediatamente le parti disponibili mentre le altre sono ancora in caricamento. Combinato con Suspense, questo meccanismo migliora drasticamente il Time to First Byte (TTFB) e l'esperienza utente percepita. ```tsx // app/product/[id]/page.tsx import { Suspense } from 'react' import { getProduct } from '@/lib/products' import ProductDetails from './ProductDetails' import ProductReviews from './ProductReviews' import RecommendedProducts from './RecommendedProducts' import { Skeleton } from '@/components/ui/Skeleton' interface ProductPageProps { params: { id: string } } export default async function ProductPage({ params }: ProductPageProps) { // This request blocks initial render const product = await getProduct(params.id) return (
{/* Immediate render with product data */} {/* Reviews load via streaming */}

Customer Reviews

}> {/* This async component will be streamed */}
{/* Recommendations too */}

Similar Products

}>
) } // Skeleton for reviews function ReviewsSkeleton() { return (
{[1, 2, 3].map((i) => (
))}
) } ``` ```tsx // app/product/[id]/ProductReviews.tsx // Async Server Component that will be streamed import { getProductReviews } from '@/lib/reviews' interface ProductReviewsProps { productId: string } export default async function ProductReviews({ productId }: ProductReviewsProps) { // This request may take time // Component will be streamed when complete const reviews = await getProductReviews(productId) if (reviews.length === 0) { return (

No reviews yet. Be the first to share your thoughts!

) } return (
{reviews.map((review) => (
{review.author} {'★'.repeat(review.rating)}{'☆'.repeat(5 - review.rating)}

{review.content}

))}
) } ``` Il browser riceve prima lo scheletro HTML con gli skeleton, poi il contenuto effettivo viene inserito progressivamente tramite streaming. ## Server Actions per le mutazioni Le Server Actions permettono di eseguire codice server da Client Components senza creare route API. Questo approccio semplifica notevolmente la gestione delle mutazioni. ```tsx // actions/cart.ts 'use server' import { revalidatePath } from 'next/cache' import { cookies } from 'next/headers' import { prisma } from '@/lib/prisma' import { getCurrentUser } from '@/lib/auth' // Action to add to cart export async function addToCart(productId: string, quantity: number = 1) { const user = await getCurrentUser() if (!user) { // Return structured error return { error: 'Login required', code: 'UNAUTHORIZED' } } try { // Check stock const product = await prisma.product.findUnique({ where: { id: productId } }) if (!product || product.stock < quantity) { return { error: 'Insufficient stock', code: 'OUT_OF_STOCK' } } // Add or update cart item await prisma.cartItem.upsert({ where: { cartId_productId: { cartId: user.cartId, productId } }, update: { quantity: { increment: quantity } }, create: { cartId: user.cartId, productId, quantity } }) // Invalidate cart page cache revalidatePath('/cart') return { success: true, message: 'Product added to cart' } } catch (error) { console.error('Add to cart error:', error) return { error: 'An error occurred', code: 'SERVER_ERROR' } } } // Action to remove from cart export async function removeFromCart(itemId: string) { const user = await getCurrentUser() if (!user) { return { error: 'Login required' } } await prisma.cartItem.delete({ where: { id: itemId, cart: { userId: user.id } } }) revalidatePath('/cart') return { success: true } } ``` ```tsx // components/AddToCartButton.tsx 'use client' import { useTransition } from 'react' import { addToCart } from '@/actions/cart' import { toast } from '@/components/ui/toast' interface AddToCartButtonProps { productId: string } export function AddToCartButton({ productId }: AddToCartButtonProps) { const [isPending, startTransition] = useTransition() const handleClick = () => { startTransition(async () => { const result = await addToCart(productId) if (result.error) { toast.error(result.error) return } toast.success(result.message) }) } return ( ) } ``` > **Validazione lato server** > > I dati nelle Server Actions vanno sempre validati. Le validazioni lato client possono essere aggirate. Librerie come Zod offrono una validazione robusta. ## Gestione degli errori ed Error Boundaries React 19 migliora la gestione degli errori con i Server Components. Gli Error Boundaries funzionano allo stesso modo dei Client Components. ```tsx // app/products/error.tsx 'use client' // Error Boundary for /products segment interface ErrorProps { error: Error & { digest?: string } reset: () => void } export default function ProductsError({ error, reset }: ErrorProps) { return (

Loading Error

Unable to load products. Please try again.

{/* Display digest for debugging */} {error.digest && (

Reference: {error.digest}

)}
) } ``` ```tsx // app/products/loading.tsx // Loading UI during initial load export default function ProductsLoading() { return (
{[1, 2, 3, 4, 5, 6].map((i) => (
))}
) } ``` Per una gestione degli errori più granulare in componenti specifici, il componente `ErrorBoundary` può essere utilizzato direttamente. ```tsx // components/ErrorBoundary.tsx 'use client' import { Component, ReactNode } from 'react' interface Props { children: ReactNode fallback?: ReactNode } interface State { hasError: boolean error?: Error } export class ErrorBoundary extends Component { constructor(props: Props) { super(props) this.state = { hasError: false } } static getDerivedStateFromError(error: Error): State { return { hasError: true, error } } componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { // Log error to monitoring service console.error('ErrorBoundary caught:', error, errorInfo) } render() { if (this.state.hasError) { return this.props.fallback || (

An error occurred

) } return this.props.children } } ``` ## Ottimizzazioni delle prestazioni in produzione Diverse tecniche consentono di ottimizzare le prestazioni dei Server Components in produzione. ```tsx // app/layout.tsx import { Suspense } from 'react' import { headers } from 'next/headers' // Preload critical data export const dynamic = 'force-dynamic' export default async function RootLayout({ children }: { children: React.ReactNode }) { return ( {/* Header streamed independently */} }>
{children}
{/* Footer can be static */}