React

Struttura di un progetto Nuxt 3: cartelle e confini

In Nuxt 3 le cartelle sono API: pages, server e composables cambiano il comportamento. Una struttura che spiega dove mettere ogni file.

In sintesi

  • Ogni cartella con nome riservato in Nuxt 3 attiva un comportamento: spostare un file cambia l’app.
  • server/ non arriva al browser, quindi le chiamate al database stanno lì.
  • components/ui/Button.vue si chiama UiButton: la sottocartella entra nel nome.

In Nuxt 3 la struttura del progetto è configurazione: ogni cartella con un nome riservato attiva un comportamento, quindi spostare un file cambia l’app. La regola utile è tenere pages/ per le rotte, server/ per il codice che il client non deve vedere e composables/ per lo stato condiviso.

Le cartelle che cambiano il comportamento

CartellaCosa attivaConfine
pages/Rotte generate dal file systemClient
server/api/Endpoint HTTP presi dal percorsoServer
server/utils/Funzioni importabili dagli handlerServer
composables/Auto-import con il nome del fileClient e server
middleware/Guardie eseguite prima del renderClient e server
plugins/Codice eseguito all’avvioDipende dal suffisso

Il suffisso del plugin decide il confine: .client.ts gira solo nel browser, .server.ts solo su Node. Senza suffisso gira in entrambi, e un accesso a window rompe la build.

Un albero che regge

app.vue
pages/
  index.vue
  blog/[slug].vue
layouts/
  default.vue
components/
  ui/            # primitive: button, input, dialog
  app/           # composizioni legate a una pagina
composables/
  useSession.ts
server/
  api/posts.get.ts
  utils/db.ts
middleware/auth.ts
nuxt.config.ts

I componenti in sottocartella prendono il nome dalla cartella: components/ui/Button.vue diventa <UiButton />. È la causa più frequente di «componente non trovato» dopo uno spostamento.

Server e client: due mondi, un repository

// server/api/posts.get.ts
export default defineEventHandler(async (event) => {
  const limit = Number(getQuery(event).limit ?? 10);
  return await db.post.findMany({ take: limit });
});

Il codice in server/ non finisce nel bundle del browser. Una chiamata al database dentro un componente client non è un errore di sintassi: è un errore a runtime, quando la build prova a includerla.

Errori che costano tempo

  • Chiamare useFetch dentro un ciclo: una richiesta per ogni elemento e la stessa chiave di cache.
  • Mettere logica di dominio in composables/ e richiamarla da server/: lo stesso file viene auto-importato in due contesti diversi.
  • Creare utils/ alla radice e aspettarsi l’auto-import nel server: gli handler usano server/utils/.
  • Dimenticare nuxi prepare dopo un clone: i tipi generati mancano e l’editor segnala errori falsi.

Lo stesso confine server/client vale negli altri framework: le form actions di SvelteKit funzionano proprio perché il codice di scrittura non arriva al browser.

Il limite noto

L’auto-import riduce le righe e nasconde le dipendenze. In un progetto con più team conviene disattivarlo per i componenti e importarli a mano, oppure accettare che la ricerca del simbolo passi dall’estensione dell’editor e non dal file.

Comandi

npx nuxi prepare
npx nuxi dev

Dal catalogo: shadcn-vue, l’icona Nuxt e il blocco logo Vue. Le guide: le librerie UI per Vue 3 e le guardie di Vue Router; il confronto: Vue contro React nel 2026.

Domande frequenti

Dove va app.vue in Nuxt 3?

Nella radice del progetto, accanto a nuxt.config.ts. Da Nuxt 4 il codice dell’app vive in app/ e app.vue si sposta dentro quella cartella.

La cartella pages è obbligatoria?

No. Senza pages l’app usa app.vue come unica pagina. La cartella serve quando vuoi le rotte generate dal file system.

Perché il mio componente non viene trovato?

Il nome include la sottocartella: components/ui/Button.vue si usa come UiButton. Sposta il file in components/ se vuoi il nome breve.

Posso usare server/utils in un componente?

No. Quel codice è compilato per il server e non entra nel bundle del browser; estrai la parte condivisa in utils/ o in un pacchetto.

Come disattivo l’auto-import?

In nuxt.config.ts imposta imports.autoImport a false per le API e components a false per i componenti, poi importa tutto a mano.