Errori e fix

Cannot find module dopo `shadcn add`: gli alias sono il problema

Il file viene aggiunto, ma TypeScript non lo trova. Nove volte su dieci l'alias in components.json non corrisponde a quello in tsconfig, oppure manca la dipendenza runtime.

In sintesi

  • `components.json` e `tsconfig.json` devono descrivere lo stesso alias: se uno usa `@/` e l'altro `~/`, il file c'è ma TypeScript non lo risolve.
  • Dopo `add`, verifica che le dipendenze della ricetta siano state installate: il componente compila solo se le sue import esistono.
  • Nei monorepo l'alias deve puntare a `src`, non alla root del pacchetto.

Risposta breve: il componente è stato scritto, ma l'import usa un alias che TypeScript non conosce. Confronta components.json con tsconfig.json: se gli alias non coincidono, il file esiste e non viene risolto.

Perché il file c'è e il compilatore non lo vede

Le due parti prendono decisioni indipendenti. Il CLI scrive il file dove dice aliases, cioè nel punto che l'autore della ricetta ha scelto. TypeScript costruisce il programma da include e rootDir, e risolve gli alias con paths relativo a baseUrl. Se il file finisce fuori da include, tsc non lo legge affatto e l'errore che stampa è Cannot find module, non not part of the project: la diagnosi parte dal messaggio sbagliato, e per questo la correzione di solito tocca l'alias quando il problema era il perimetro.

Le quattro cause

CausaSintomoCorrezione
Alias diverso tra i due fileimport rosso, file presenteallinea `aliases` e `paths`
`baseUrl` assentetutti gli alias fallisconoaggiungi `baseUrl: "."`
Dipendenza non installataerrore su un pacchettoinstalla le dipendenze elencate
Monorepo con alias sulla rootfunziona in un pacchetto, non nell'altropunta a `src` del pacchetto

Confronto diretto

// components.json
{
  "$schema": "https://ui.shadcn.com/schema.json",
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils",
    "ui": "@/components/ui"
  }
}
// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

Se components.json dice @/components e tsconfig mappa @/* su ./src/*, il file finisce in src/components e l'import funziona. Se uno dei due usa ~/, il file viene scritto in un posto e cercato in un altro.

La sequenza di verifica

  1. Trova il file appena creato: find . -name "button.tsx" -not -path "*/node_modules/*".
  2. Apri components.json e annota gli alias.
  3. Apri tsconfig.json e verifica che ogni alias abbia il suo paths.
  4. Esegui npx tsc --noEmit: l'errore indica il percorso cercato.

Il comando che dice dove guarda TypeScript

# ogni tentativo di risoluzione, con il percorso cercato
npx tsc --noEmit --traceResolution | grep "button"

# PowerShell
npx tsc --noEmit --traceResolution | Select-String "button"

L'output è lungo e utile: per ogni import elenca i candidati nell'ordine in cui paths li propone, e chiude con la riga che dice se ha risolto. Cerca la prima riga not resolved e leggi il percorso che ha provato. Se il percorso è giusto e il file non c'è, il CLI ha scritto altrove; se il percorso è sbagliato, l'alias è sbagliato. In entrambi i casi il file di configurazione da aprire è deciso dall'output, non dall'intuito.

Quando la correzione non funziona

Se l'alias è corretto e l'errore resta, il problema può essere il resolver del bundler: Vite e Next.js leggono paths in modo diverso e alcuni setup richiedono anche vite-tsconfig-paths. Controlla che il file di configurazione del bundler estenda lo stesso tsconfig. Nei monorepo, un pacchetto può avere il proprio tsconfig e ignorare quello della root.

Quando la correzione tipica peggiora le cose

La correzione che si trova nei forum è aggiungere un secondo schema ai paths: "@/*": ["./src/*", "./*"], così qualunque posizione funziona. Funziona per il caso di oggi e introduce due sorgenti valide per lo stesso nome. TypeScript prende il primo che esiste, il bundler può prendere l'altro, e il progetto inizia a compilare contro una copia vecchia di un componente rimasta nella root da un init precedente. La build passa, la pagina mostra il componente sbagliato, e il sintomo somiglia a una cache. È l'unico caso di questa lista in cui il rosso sparisce e il difetto resta.

Il limite dell'allineamento

Un limite da dichiarare: allineare gli alias risolve la risoluzione, non il contratto della ricetta. Il CLI non riscrive import arbitrari dentro il codice che copia, quindi una ricetta di terze parti può importare un percorso che nel tuo progetto non esiste, o esiste con un altro nome. Il componente compila e manca un pezzo. Dopo ogni add la verifica completa è due comandi, non uno: il controllo dei tipi e l'apertura della pagina.

Come prevenirlo

  • Dopo ogni add, esegui tsc --noEmit in CI: un import rotto non arriva in produzione.
  • Tieni un solo alias nel progetto: due convenzioni convivono male.
  • Nel monorepo, un test che crea un componente vuoto e lo importa dall'app consumatrice.
  • Un solo paths con una sola voce per alias: due candidati per lo stesso nome sono un guasto che aspetta.

Dove continuare

Per capire il sistema: componenti, temi, blocchi e blocco newsletter. Le guide: temi gratuiti e premium e struttura App Router; il confronto: registry privati, token e namespace.

Domande frequenti

Il file esiste ma l'import è rosso: cosa controllo?

Apri `components.json`, guarda `aliases.components` e `aliases.utils`, poi confrontali con `compilerOptions.paths` in tsconfig. Devono descrivere lo stesso percorso.

Perché funziona con Vite e non con Next.js?

I due progetti hanno alias iniziali diversi e un tsconfig diverso. La ricetta usa l'alias dichiarato in components.json: se manca, applica quello di default.

Devo riavviare il dev server?

Per un file nuovo spesso sì, perché il resolver tiene una cache dei percorsi. Se dopo il riavvio resta rosso, il problema è di configurazione, non di cache.

Cosa significa `Missing dependency` nell'output?

Che la ricetta importa un pacchetto non presente nel progetto. Installa quello che il comando elenca, poi riprova.

Posso spostare i componenti fuori da `src`?

Sì, ma aggiorna alias e `content` di Tailwind insieme. Spostare solo i file produce componenti che esistono e non vengono stilizzati.