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

StrumentoCosa dichiaraCosa blocca
`"use client"`Un modulo client e il suo sottoalberoNiente: il server può importarlo
`"use server"`Una funzione azioneNiente: il client riceve un riferimento
`server-only`Un modulo che vive solo sul serverL'import dal grafo client
`client-only`Un modulo che vive solo nel browserL'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.