React
Structuring a Nuxt 3 project: folders and boundaries
In Nuxt 3 folders are APIs: pages, server and composables change behaviour. A structure that says where each file belongs.
Key points
- Every reserved-name folder in Nuxt 3 turns on a behaviour, so moving a file changes the app.
- server/ never reaches the browser, so database calls belong there.
- components/ui/Button.vue is called UiButton: the subfolder becomes part of the name.
In Nuxt 3 the project structure is configuration: every folder with a reserved name turns on a behaviour, so moving a file changes the app. The useful rule is to keep pages/ for routes, server/ for code the client must never see and composables/ for shared state.
The folders that change behaviour
| Folder | What it turns on | Boundary |
|---|---|---|
| pages/ | Routes generated from the file system | Client |
| server/api/ | HTTP endpoints named after the path | Server |
| server/utils/ | Functions handlers can import | Server |
| composables/ | Auto-import from the file name | Client and server |
| middleware/ | Guards that run before the render | Client and server |
| plugins/ | Code that runs at startup | Depends on the suffix |
The plugin suffix decides the boundary: .client.ts runs only in the browser, .server.ts only on Node. Without a suffix it runs in both, and one access to window breaks the build.
A tree that holds
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
Components in a subfolder take their name from that folder: components/ui/Button.vue becomes <UiButton />. This is the most common cause of the «component not found» error after a move.
Server and client: two worlds, one 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 });
});
Code in server/ never reaches the browser bundle. A database call inside a client component is not a syntax error: it fails at runtime, when the build tries to include it.
Mistakes that cost time
- Calling
useFetchinside a loop: one request per item and the same cache key for all of them. - Putting domain logic in
composables/and calling it fromserver/: the same file gets auto-imported into two different contexts. - Creating
utils/at the root and expecting the server to auto-import it: handlers useserver/utils/. - Skipping
nuxi prepareafter a clone: the generated types are missing and the editor reports false errors.
The same server/client boundary applies in other frameworks: SvelteKit form actions work precisely because the write path never reaches the browser.
The known limitation
Auto-import saves lines and hides dependencies. In a project with several teams, turn it off for components and import them by hand, or accept that finding a symbol happens through the editor extension rather than the file.
Commands
npx nuxi prepare
npx nuxi dev
From the catalogue: shadcn-vue, the Nuxt icon and the Vue logo block. The guides: UI libraries for Vue 3 and Vue Router navigation guards; the comparison: Vue against React in 2026.
Frequently asked questions
Where does app.vue go in Nuxt 3?
In the project root, next to nuxt.config.ts. From Nuxt 4 the app code lives in app/ and app.vue moves inside that folder.
Is the pages folder mandatory?
No. Without pages the app uses app.vue as its only page. The folder earns its place when you want routes generated from the file system.
Why is my component not found?
The name includes the subfolder: components/ui/Button.vue is used as UiButton. Move the file into components/ if you want the short name.
Can I use server/utils in a component?
No. That code is compiled for the server and never enters the browser bundle; extract the shared part into utils/ or a package.
How do I turn off auto-import?
In nuxt.config.ts set imports.autoImport to false for the APIs and components to false for components, then import everything by hand.