# 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!
)
}
```
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 */}
)
}
```
```tsx
// lib/prefetch.ts
// Data prefetching for links
import { preload } from 'react-dom'
export function prefetchProduct(productId: string) {
// Prefetch images
preload(`/api/products/${productId}/image`, { as: 'image' })
}
// Usage in a component
// prefetchProduct(id)}>
```
```tsx
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
// Build optimizations
experimental: {
// Enable Partial Prerendering (PPR)
ppr: true,
// Optimize package imports
optimizePackageImports: ['lucide-react', '@radix-ui/react-icons']
},
// Image configuration
images: {
formats: ['image/avif', 'image/webp'],
remotePatterns: [
{ hostname: 'cdn.example.com' }
]
}
}
module.exports = nextConfig
```
## Conclusione
I Server Components di React 19 trasformano radicalmente il modo in cui vengono costruite le applicazioni React. I punti chiave:
- Separazione server/client: utilizzare `"use client"` solo per i componenti interattivi
- Recupero dati diretto: `async/await` nei componenti, senza useEffect o route API
- Streaming con Suspense: visualizzazione progressiva per una UX percepita migliore
- Server Actions: mutazioni semplificate senza creare endpoint API dedicati
- Caching intelligente: `revalidate` e `tags` per l'ottimizzazione delle prestazioni
- Composizione flessibile: pattern `children` per combinare Server e Client Components
Questa architettura consente di creare applicazioni più performanti con meno JavaScript lato client, semplificando significativamente il codice. La transizione ai Server Components rappresenta un investimento che ripaga rapidamente in termini di prestazioni e manutenibilità.
---
Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack.
HTML version of this page: https://sharpskill.dev/it/blog/react-next/react-19-server-components-production