50% off for the first 50 customers. Only 10 left. Use code
40/50 claimed
See pricing →
November 2, 2026

Nuxt 4 Folder Structure Explained

The Nuxt 4 folder structure explained: what goes in app/, server/, shared/ and public/, and where Quasar config, plugins and theme files belong.
Nuxt
Nuxt 4
Project Structure
Quasar

The Nuxt 4 folder structure moves your Vue code into an app/ folder and leaves server/, shared/ and public/ at the root. It's a small change on paper. In practice it decides where every file in your project goes, and older tutorials put things in the wrong place. This post walks through each folder, what it is for, and where the Quasar pieces of a Nuxt app (config, plugins, Sass variables, theme files, components) belong.

The examples come from the layout we use for quasarui.com, which runs on Nuxt 4 with nuxt-quasar-ui.

The Nuxt 4 project structure at a glance

my-app/
├─ app/
│  ├─ assets/
│  ├─ components/
│  ├─ composables/
│  ├─ layouts/
│  ├─ middleware/
│  ├─ pages/
│  ├─ plugins/
│  ├─ utils/
│  ├─ app.config.ts
│  ├─ app.vue
│  └─ error.vue
├─ content/          # only with @nuxt/content
├─ layers/
├─ modules/
├─ public/
├─ server/
│  ├─ api/
│  ├─ middleware/
│  ├─ plugins/
│  ├─ routes/
│  └─ utils/
├─ shared/
│  ├─ types/
│  └─ utils/
└─ nuxt.config.ts

The rule behind it: app/ is the code that runs in the Vue app, server/ is the code that runs in Nitro, and shared/ is the code both of them can use. Everything else at the root is config, static files or build-time code.

Why Nuxt 4 added the app directory

In Nuxt 3, pages/, components/ and friends sat at the project root, next to node_modules/ and .git/. The Nuxt team gives two reasons for moving them:

  • Speed. File watchers no longer scan the whole root, which the Nuxt docs say can slow startup noticeably on Windows and Linux.
  • Type safety. The Vue app and the server have different globals and auto-imports. With them in separate folders, your editor can type each side correctly.

Because the default source folder is now app/, the ~ and @ aliases point to app/. So ~/assets/main.scss means app/assets/main.scss. To reach the project root, use ~~ (or @@).

If you upgrade a Nuxt 3 project and leave the folders where they are, Nuxt detects the old layout and keeps working. Moving them is still worth doing.

What goes in app/

This is where most of your code lives.

  • pages/: file-based routes. app/pages/orders/[id].vue becomes /orders/:id.
  • layouts/: page frames, picked per page with definePageMeta({ layout: "dashboard" }).
  • components/: auto-imported Vue components. Nested folders become part of the name, so app/components/form/EmailField.vue is used as <FormEmailField />.
  • composables/: auto-imported composables. Only top-level files are scanned. A file in app/composables/billing/ needs to be re-exported from a top-level file, or added to imports.dirs.
  • utils/: auto-imported helper functions for the app.
  • middleware/: route middleware that runs before navigating to a page.
  • plugins/: code that runs once when the app starts. Only top-level files are registered.
  • assets/: files the build tool processes: Sass, CSS, fonts, images you import.
  • app.vue, error.vue and app.config.ts: the root component, the error page and reactive app config.

app.config.ts lives inside app/ in Nuxt 4, not at the root. It is a common mistake when following a Nuxt 3 tutorial.

What goes in server/

server/ is the Nitro server: your API, server middleware and server-only helpers. It never ships to the browser.

server/
├─ api/
│  └─ orders/
│     ├─ index.get.ts     # GET  /api/orders
│     └─ index.post.ts    # POST /api/orders
├─ routes/
│  └─ sitemap.xml.ts      # GET  /sitemap.xml
├─ middleware/
├─ plugins/
└─ utils/
  • api/: handlers served under /api. A .get.ts or .post.ts suffix limits a handler to one HTTP method.
  • routes/: handlers without the /api prefix, such as sitemap.xml or robots.txt.
  • middleware/: runs on every request before the handlers. Use it to read or add to the request context, not to send a response.
  • plugins/: Nitro plugins that hook into the server's lifecycle.
  • utils/: helpers for your handlers, such as a database client or an access check.

A minimal handler:

// server/api/health.get.ts
export default defineEventHandler(() => ({ ok: true }));

Secrets like API keys belong here, read through runtime config. Anything under app/ can end up in the browser bundle.

What goes in shared/

shared/ holds code that both the Vue app and the server import. It arrived in Nuxt 3.14 and is part of the default layout in Nuxt 4.

Nuxt auto-imports the top-level files of shared/utils/ and shared/types/, in the app and on the server. Anything else under shared/ is imported with the #shared alias:

import { formatPrice } from "#shared/format/price";

The rule that keeps it working: code in shared/ can't import Vue or Nitro code. No ref, no useFetch, no defineEventHandler. Plain functions, constants and types only.

That makes it the right home for anything that has to agree on both sides:

  • Validation rules. Write isValidEmail() once in shared/utils/, use it in a QInput's :rules on the page and in the API handler that saves the form.
  • Types. An Order interface used by the page and returned by server/api/orders.
  • Constants. Plan names, status lists, limits.

On quasarui.com, shared/utils/ holds things like the catalogue helpers and the launch offer, which pages render and the server checks.

What goes in public/

public/ is served as is from the site root. public/img/logo.svg is available at /img/logo.svg. Nothing in it is processed or renamed.

Use it for files that need a fixed URL: favicon.ico, robots.txt, Open Graph images, and images you reference by path in templates. Use app/assets/ instead for files you import into code, which the build can optimize and fingerprint.

The other root folders

  • content/: Markdown for @nuxt/content. This blog post is a file in content/blogs/.
  • layers/: local Nuxt layers. Each one is a mini Nuxt project that gets merged into yours.
  • modules/: local Nuxt modules that run at build time.
  • nuxt.config.ts: the project config. It stays at the root.

Build output (.nuxt/, .output/) is generated. Keep it out of git.

Where Quasar files go in a Nuxt 4 app

With nuxt-quasar-ui, a Quasar app in Nuxt has the same folders as any other Nuxt app. Here is where each Quasar piece lives. If you haven't installed the module yet, the Nuxt + Quasar setup guide covers that first.

Quasar config: nuxt.config.ts

There is no quasar.config file. Everything you would put there goes in the quasar key of nuxt.config.ts: plugins, icon sets, the dark mode default, brand colors. Ours, trimmed:

export default defineNuxtConfig({
  modules: ["nuxt-quasar-ui"],
  css: ["~/assets/main.scss"],
  quasar: {
    plugins: ["Notify", "Dialog"],
    sassVariables: "~/assets/quasar-variables.scss",
    extras: {
      font: null,
      fontIcons: ["material-icons", "mdi-v7"],
    },
  },
});

Sass variables and theme CSS: app/assets/

sassVariables points to ~/assets/quasar-variables.scss, which in Nuxt 4 is app/assets/quasar-variables.scss. That file sets $primary, $secondary, $dark and the rest.

If you export a theme from the Theme Builder for Nuxt, its CSS file goes in app/assets/css/ and is listed in css in nuxt.config.ts. Same folder rule: the build processes it, so it belongs in assets/, not public/.

Boot files: app/plugins/

A Quasar CLI app has src/boot/ for code that runs before the app mounts. In Nuxt, that is a plugin in app/plugins/:

// app/plugins/notify-defaults.client.ts
import { Notify } from "quasar";

export default defineNuxtPlugin(() => {
  Notify.setDefaults({ position: "top-right", timeout: 2500 });
});

The .client suffix runs it in the browser only. Plugins run in alphabetical order of their file names, so prefix them (01.auth.ts, 02.notify.ts) when one depends on another.

Components: app/components/

Your own Quasar components, and any you copy from a library, go in app/components/. Nuxt auto-imports them, so app/components/PricingSection01.vue is ready to use as <PricingSection01 /> in any page. Group them in subfolders once there are many, and remember that the folder becomes part of the name.

Layouts: app/layouts/

A Quasar QLayout with its header and drawer goes in a Nuxt layout, such as app/layouts/dashboard.vue, with <slot /> inside its QPageContainer. Pages then pick it with definePageMeta.

How big projects split app/

Folders tell you where a file goes. Inside app/, you still have to decide how to split features: one composable per domain, components grouped by feature, pages kept thin. The post on structuring Quasar projects covers those choices. They apply unchanged inside app/.

Nuxt 4 folder structure FAQ

Do I have to use the app/ folder? No. Nuxt detects the Nuxt 3 layout and keeps working, and you can force it with srcDir: "." and dir: { app: "app" }. New projects should use app/.

Where do tests go? The Nuxt docs suggest a test/ folder at the root. Keep them out of app/ so they aren't picked up as app code.

Can a page import from server/? It shouldn't. Pages talk to the server over HTTP with $fetch or useFetch, and code that both sides need goes in shared/.

Why is my composable not auto-imported? It's probably in a subfolder of app/composables/. Only top-level files are scanned.

Start from a finished app

If you would rather start from a working Quasar app than an empty tree, the QuasarUI Templates are complete multi-page Quasar admin dashboards with charts, tables, dark mode and the folder layout already decided. They are built with the Quasar CLI rather than Nuxt, and each one comes as its own private GitHub repository.