# React Server Components in produzione: pattern e insidie
> React Server Components in produzione: pattern collaudati, anti-pattern comuni e strategie di debug per applicazioni Next.js 15 robuste.
- Published: 2026-03-29
- Updated: 2026-05-04
- Author: Anthony Fillion-Maillet
- Tags: react server components, next.js 15, rsc patterns, production, react 19
- Reading time: 9 min
---
I React Server Components (RSC) cambiano in modo radicale come funziona il rendering lato server in Next.js 15, ma l'adozione in produzione rivela insidie che la documentazione ufficiale non sempre copre. Questo articolo scompone i pattern che funzionano, quelli che si rompono e come diagnosticare i problemi prima che raggiungano la produzione.
> **Server Components e Client Components**
>
> Un Server Component viene eseguito esclusivamente sul server e invia zero JavaScript al browser. Un Client Component (marcato con `"use client"`) viene eseguito su entrambi i lati. La regola: mantenere i Client Components il più piccoli possibile e il più in basso possibile nell'albero.
## Il confine server-client: capire il pattern di boundary
L'insidia più comune con gli RSC riguarda il confine tra Server e Client Components. Una volta che un componente porta la direttiva `"use client"`, **tutti i suoi figli importati diventano Client Components**, anche senza la direttiva.
```tsx
// ProductPage.tsx (Server Component)
import { ProductDetails } from './ProductDetails'
import { AddToCartButton } from './AddToCartButton'
export default async function ProductPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const product = await getProduct(id)
return (
{/* Server Component: accesso diretto al DB */}
{/* Client Component: interattività isolata */}
)
}
```
```tsx
// AddToCartButton.tsx (Client Component)
'use client'
import { useState } from 'react'
export function AddToCartButton({ productId, price }: { productId: string; price: number }) {
const [adding, setAdding] = useState(false)
async function handleAdd() {
setAdding(true)
await fetch('/api/cart', {
method: 'POST',
body: JSON.stringify({ productId, quantity: 1 }),
})
setAdding(false)
}
return (
)
}
```
Il pattern chiave: passare i dati come **prop serializzabili** dal Server Component al Client Component. Funzioni, classi e oggetti Date non possono attraversare questo confine.
## Anti-pattern: il wrapper Client Component non necessario
Un errore frequente è creare un Client Component che incapsula figli Server Component, costringendo l'intero sottoalbero al lato client.
```tsx
// PageWrapper.tsx — ANTI-PATTERN
'use client'
import { useState } from 'react'
// Tutto il contenuto figlio diventa lato client
export function PageWrapper({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState('light')
return (
{children}
)
}
```
> **children come slot**
>
> La soluzione: passare i Server Components come `children` (pattern slot). I figli passati come prop restano Server Components anche quando il padre è un Client Component. Il codice sopra funziona correttamente finché `children` proviene da un padre Server Component.
```tsx
// layout.tsx (Server Component)
import { PageWrapper } from './PageWrapper'
import { HeavyServerContent } from './HeavyServerContent'
export default function Layout() {
return (
{/* Resta Server Component nonostante il wrapper client */}
)
}
```
Questo pattern di composizione preserva i benefici del rendering lato server per i contenuti pesanti, abilitando al contempo l'interattività a livello di wrapper.
## Gestione dati asincroni: il pattern fetch-nel-componente
React 19 e Next.js 15 supportano `async/await` direttamente nei Server Components. Questo pattern semplifica il fetching dei dati rispetto al vecchio approccio `getServerSideProps`.
```tsx
// UserProfile.tsx (Server Component)
import { cache } from 'react'
// Deduplica chiamate identiche all'interno dello stesso render
const getUser = cache(async (userId: string) => {
const res = await fetch(`https://api.example.com/users/${userId}`, {
next: { revalidate: 3600 }, // Cache per 1 ora
})
if (!res.ok) throw new Error('User not found')
return res.json()
})
export default async function UserProfile({ userId }: { userId: string }) {
const user = await getUser(userId)
return (
{user.name}
{user.email}
Membro dal {new Date(user.createdAt).toLocaleDateString('it-IT')}
)
}
```
Tre punti critici:
- Il `cache()` di React deduplica chiamate identiche durante un singolo render server
- `next: { revalidate }` controlla la durata della cache lato Next.js
- Gli errori in un Server Component asincrono attivano il `error.tsx` più vicino
## Insidia di serializzazione: cosa non attraversa il confine
I dati scambiati tra Server e Client Components devono essere serializzabili in JSON. Ecco cosa provoca errori silenziosi o crash.
```tsx
// INSIDIA: passare tipi non serializzabili
// Funzione — non funziona
{ /* server action */ }} />
// Usare una Server Action importata
import { submitForm } from '@/lib/actions/form'
// Oggetto Date — non funziona
// Stringa ISO — funziona
// Map, Set, RegExp — non funziona
// Oggetto semplice o array — funziona
```
Le Server Actions (funzioni marcate con `"use server"`) sono l'eccezione: possono essere passate come prop a un Client Component perché Next.js le trasforma in endpoint HTTP.
## Streaming e Suspense: pattern di caricamento progressivo
Lo streaming SSR con Suspense invia HTML al browser in modo progressivo. Il pattern ottimale usa boundary Suspense granulari intorno a ogni sezione asincrona.
```tsx
// DashboardPage.tsx (Server Component)
import { Suspense } from 'react'
import { RevenueChart } from './RevenueChart'
import { RecentOrders } from './RecentOrders'
import { UserStats } from './UserStats'
export default function DashboardPage() {
return (
}>
}>
}>
)
}
```
Ogni sezione si carica in modo indipendente. Se `RevenueChart` impiega 3 secondi e `UserStats` 200 ms, le statistiche appaiono all'istante senza attendere il grafico.
> **Suspense e SEO**
>
> Il contenuto all'interno di una boundary Suspense è renderizzato lato server e incluso nell'HTML iniziale. I crawler vedono il contenuto completo. Lo streaming influisce solo sulla velocità di consegna al browser, non sulla visibilità SEO.
## Debug in produzione: tracciare i problemi RSC
Gli errori RSC sono spesso criptici. Tre tecniche diagnostiche funzionano in produzione.
**1. Identificare i mismatch di idratazione**
```tsx
// debug-hydration.tsx
'use client'
import { useEffect, useState } from 'react'
export function HydrationDebug() {
const [isClient, setIsClient] = useState(false)
useEffect(() => {
setIsClient(true)
}, [])
if (process.env.NODE_ENV !== 'development') return null
return (
{isClient ? 'Client' : 'Server'}
)
}
```
**2. Loggare il payload RSC**
In Next.js 15, abilitare il logging RSC in `next.config.ts`:
```typescript
// next.config.ts
const nextConfig = {
logging: {
fetches: {
fullUrl: true, // Mostra le URL complete dei fetch
},
},
}
export default nextConfig
```
**3. Controllare la dimensione del payload**
Un payload RSC sovradimensionato (> 128 KB) degrada le prestazioni. Monitorare le richieste di rete con il content type `text/x-component` nei DevTools.
## Pattern avanzato: composizione con Server Actions
Le Server Actions combinate con i Server Components creano un pattern CQRS naturale: letture sul server (RSC), scritture tramite action.
```tsx
// TodoList.tsx (Server Component)
import { getTodos } from '@/lib/services/todo'
import { TodoForm } from './TodoForm'
import { deleteTodo } from '@/lib/actions/todo'
export default async function TodoList() {
const todos = await getTodos()
return (
{todos.map(todo => (
{todo.title}
))}
)
}
```
```tsx
// actions/todo.ts
'use server'
import { revalidatePath } from 'next/cache'
import { TodoService } from '@/lib/services/todo'
export async function deleteTodo(formData: FormData) {
const id = formData.get('id') as string
await TodoService.delete(id)
revalidatePath('/todos')
}
```
La chiamata a `revalidatePath` innesca un nuovo render del Server Component con dati aggiornati, senza un ricaricamento completo della pagina.
Per una preparazione al colloquio più approfondita su questi argomenti, consultare il modulo [Next.js Server Actions](/technologies/react-next/interview-questions/nextjs-server-actions) e il modulo [Next.js Data Fetching](/technologies/react-next/interview-questions/nextjs-data-fetching) su SharpSkill. La [documentazione ufficiale React](https://react.dev/reference/rsc/server-components) copre l'intera specifica dei Server Components.
## Conclusione
- Mantenere i Client Components piccoli e isolati nella parte bassa dell'albero dei componenti
- Usare il pattern slot (`children`) per preservare i Server Components dentro un wrapper client
- Verificare sempre la serializzabilità delle prop attraverso il confine server-client
- Posizionare boundary Suspense granulari intorno a ogni sezione asincrona indipendente
- Monitorare la dimensione del payload RSC in produzione (target < 128 KB)
- Combinare Server Components (letture) e Server Actions (scritture) per un pattern CQRS naturale
- Usare il `cache()` di React per deduplicare le richieste all'interno di un singolo render server
---
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-server-components-patterns-pitfalls