React
server-only e client-only: gli import che rompono la build
Due pacchetti da una riga che trasformano un import sbagliato in un errore di build. Dove metterli, cosa bloccano e cosa non proteggono.
In sintesi
- `server-only` blocca l'import di un modulo server dal grafo client.
- `client-only` fa l'opposto per i moduli che leggono il browser.
- Le direttive `"use client"` e `"use server"` dichiarano, non proteggono.
- Proteggono il modulo, non il valore: un segreto passato come prop esce comunque.
`server-only` e `client-only` trasformano un import sbagliato in un errore di build. Non cambiano il runtime: impediscono al bundler di mettere un modulo nel grafo che non gli appartiene.
Come si installano
npm install server-only client-only
Sono due pacchetti vuoti con un `exports` condizionale: la condizione `react-server` risolve un file che lancia. Il resto del lavoro lo fa il bundler, che gira anche in CI.
Il caso classico: un client che importa il database
// lib/db.ts
import "server-only";
import { PrismaClient } from "@prisma/client";
const globalForDb = globalThis as unknown as { db?: PrismaClient };
export const db = globalForDb.db ?? new PrismaClient();
if (process.env.NODE_ENV !== "production") globalForDb.db = db;
La prima riga è tutta la protezione. Se un client component importa `lib/db.ts`, la build si ferma con un messaggio che nomina il modulo e il percorso. Senza quella riga il bundle proverebbe a includere il driver, e a volte ci riesce.
// lib/seed.ts
import "server-only";
import { db } from "./db";
export async function seedDemo() {
const count = await db.user.count();
if (count > 0) return { skipped: true };
await db.user.createMany({ data: [{ email: "[email protected]" }] });
return { skipped: false };
}
Metti l'import in cima a ogni modulo che tocca segreti, filesystem, variabili d'ambiente private o driver di database. Anche nei file che sembrano innocui: uno script di seed importa il db, e prima o poi qualcuno lo importa da una pagina.
`client-only` è più raro, ma serve
// components/local-time.tsx
"use client";
import "client-only";
export function LocalTime({ iso }: { iso: string }) {
const formatted = new Intl.DateTimeFormat("it-IT", {
dateStyle: "full",
timeStyle: "short",
}).format(new Date(iso));
return {formatted};
}
Un componente che legge il fuso orario, il `localStorage` o una API del browser non può essere renderizzato sul server: il risultato cambierebbe tra server e client e React segnalerebbe un mismatch di idratazione.
Direttive e pacchetti non sono la stessa cosa
| Strumento | Cosa dichiara | Cosa blocca |
|---|---|---|
| `"use client"` | Un modulo client e il suo sottoalbero | Niente: il server può importarlo |
| `"use server"` | Una funzione azione | Niente: il client riceve un riferimento |
| `server-only` | Un modulo che vive solo sul server | L'import dal grafo client |
| `client-only` | Un modulo che vive solo nel browser | L'import dal grafo server |
Le direttive descrivono l'intenzione, i pacchetti la impongono. Una direttiva dimenticata produce un errore a runtime; il pacchetto lo produce alla build, quando costa meno.
Il numero che conta
Il catalogo EneUI indicizza 111.985 componenti e 1.556 blocchi da 1.472 librerie, misurati il 1 novembre 2026 su `/api/stats`. Molti componenti del catalogo leggono il DOM e vogliono `client-only`: hover card, animated loader e form validation.
Il caso che rompe davvero è un hook importato da un server component: leggi server component che importa un hook client. Per l'errore `window is not defined` c'è finestra non definita in Next.js. Per il confine tra mutazioni e letture Route Handlers vs Server Actions. Per il confronto tra framework Remix vs Next.js.
Limite noto: `server-only` protegge il modulo, non il valore. Se un server component passa un segreto come prop a un client component, il pacchetto non dice nulla: il dato attraversa il confine nella risposta HTML.
Domande frequenti
A cosa serve `server-only` se esiste già `"use server"`?
Non sono la stessa cosa. `"use server"` dichiara una funzione azione; `server-only` impedisce che un modulo server finisca nel bundle del client.
L'errore compare in sviluppo o in build?
In entrambi, perché il bundler risolve la condizione `react-server` a ogni compilazione. In CI l'errore blocca il deploy, che è il momento giusto.
Devo metterlo in ogni file che usa il database?
In ogni file che importa il driver o un segreto. Un wrapper che riesporta `db` ha bisogno della stessa riga, altrimenti riapre il buco.
`client-only` serve se il file ha `"use client"`?
Sì, quando il modulo non può proprio girare sul server. `"use client"` permette al server di importarlo per il prerendering; `client-only` lo vieta.
Funziona anche fuori da Next.js?
Sì, i pacchetti sono di React e si appoggiano alla condizione `react-server` del bundler. Con Vite o un altro bundler serve la stessa condizione.