Home / Blog / Vue.js Project Structure: A Feature-Based Architecture That Scales
Vue.js Project Structure: A Feature-Based Architecture That Scales

Vue.js Project Structure: A Feature-Based Architecture That Scales

This entry is part 1 of 3 in the series How to Structure a Large Scale Vue.js Application

Most "how to structure a Vue app" advice hands you a list of folders and stops there. That is fine until the app gets big, and then the folder names stop being the problem. The problem becomes prediction: a new developer opens the repo and cannot guess where the login form lives, where its store lives, or where to put the next feature. Files drift, components/ turns into a junk drawer, and every code review argues about placement.

This guide gives you one opinionated default that scales, a directory tree you can copy, and two rules that keep it predictable. Then it answers the question the folder list never does: when do you actually split an app into feature modules, add shared layers, or move to a monorepo? If you want the short answer, read the recommendation below the tree. If you want the reasoning, the rest of the article backs it up.

Reference Architecture & Decision Guide

Start with the decision. The right structure depends on the size and shape of the app, not on taste:

Your situation Recommended structure Why
Prototype or small app, 1 to 2 devs, under ~20 components, one domain Flat layered structure (components/, composables/, stores/, pages/) Feature folders add ceremony you do not need yet. Stay flat until it hurts.
Growing app, several domains (auth, billing, dashboard) in one codebase Feature-based modules under src/features/ Keep each domain's components, store, and API in one folder so features stay self-contained.
Many domains sharing a design system and utilities Feature modules + a shared/ layer Cross-cutting UI and helpers live in shared/, never inside a single feature.
Multiple deployable apps (admin, customer, marketing) sharing code Monorepo (pnpm or npm workspaces, with Turborepo or Nx) Share packages without publishing to a registry or copy-pasting between repos.
One app, but separate teams own slices and build times hurt Consider Nuxt layers before a full monorepo Lighter than workspaces. Verify the tradeoffs against current tooling docs before committing.

The recommended default for anything you would call large-scale: feature-based modules. Here is the reference tree. Copy it and rename the feature folders to match your domains:

src/
├── app/                  # app-level wiring only
│   ├── router/           # root router; imports each feature's routes
│   ├── plugins/          # pinia, i18n, third-party setup
│   └── App.vue
├── shared/               # cross-feature and domain-agnostic
│   ├── ui/               # BaseButton.vue, BaseModal.vue (the design system)
│   ├── composables/      # useBreakpoint, useClipboard
│   ├── lib/              # http client, date/format helpers
│   └── types/
├── features/             # the heart of the app: one folder per domain
│   ├── auth/
│   │   ├── components/    # LoginForm.vue and other auth-only components
│   │   ├── composables/   # useAuth
│   │   ├── stores/        # auth store (Pinia)
│   │   ├── api/           # auth requests
│   │   ├── pages/         # LoginPage.vue and other route-level views
│   │   ├── routes.ts      # this feature's routes, merged in app/router
│   │   └── types.ts
│   ├── billing/          # same shape as auth/
│   └── dashboard/        # same shape as auth/
├── assets/
└── main.ts

Two Rules for Scalable Architecture

  1. A file that only one feature uses lives inside that feature. The moment a second feature needs it, promote it to shared/. Nothing domain-specific goes in shared/.
  2. Features do not reach into each other's internals. If billing needs something from auth, it goes through a shared store, a route param, or an emitted event, not a direct import of auth's components. This is what stops a large app from turning into one tangled graph.

Building anything you would call large-scale? Use feature-based modules (the tree above). Group by domain, not by file type, so each feature owns its components, store, API calls, and routes. New developers learn one feature and understand the whole shape of the app.

Under roughly 20 to 30 components with a single domain? Stay flat. Use components/, composables/, stores/, and pages/ at the top level. Do not adopt feature folders on day one; migrate to them when a second real domain appears.

Genuinely running multiple apps that share code (admin plus customer plus a marketing site)? Go monorepo. Use pnpm or npm workspaces, and reach for Turborepo or Nx when build orchestration and caching start to matter. A monorepo is for multiple deployables, not for one big app.

Keep the flat shared component directory the original article championed, but scope it to base and UI components in shared/ui/. That advice was right for shared components and wrong when applied to everything.

The Principle of Predictability: Why Structure Matters

What is the best way to structure a Vue.js application so that it scales and remains maintainable and extendable as it grows? The answer lies in the principle of predictability.

Predictability is the ability to intuitively go from a feature request or bug report directly to the location in the codebase where that task should be addressed. Furthermore, it means immediately knowing what tools and composables you have access to at that location.

Without predictability, opening a large codebase leads to paralysis: "Where does this feature live? Should this component be global? Where is the state managed?" A predictable architecture eliminates friction, making onboarding faster and long-term maintenance significantly more efficient.

Scaffolding & Modern Stack Defaults

When starting a new Vue 3 project, legacy tooling like Vue CLI has been superseded by Vite and the official scaffolding tool create-vue:

npm create vue@latest

Modern Vue 3 applications adopt three primary defaults:

  • Composition API with <script setup>: The standard authoring format for concise, performant components. Learn more in Vue School's Vue 3 Composition API course.
  • Pinia for State Management: Replacing Vuex, Pinia is Vue's official store library, offering full TypeScript integration and zero boilerplate. Explore Pinia: The Enjoyable Vue Store on Vue School.
  • Vue Router 4: Co-locating feature routes (e.g. routes.ts) and registering them in the root router keeps routing modular. See Vue Router 4 for Everyone.

Organizing Components: Base UI vs. Feature Components

The Vue Style Guide provides essential naming rules for maintainable applications:

  • Single File Components (SFCs) should be named in PascalCase.
  • Base UI components should begin with a consistent prefix like Base or App (e.g., BaseButton.vue, BaseModal.vue) and reside in shared/ui/. Read more in Vue School's guide to Component Design Patterns.
  • Single-instance components should begin with The (e.g., TheHeader.vue, TheSidebar.vue).
  • Domain components should be multi-word and nested directly within their feature module (e.g., src/features/auth/components/LoginForm.vue).

Standardized Route and Page Naming

For resource-driven applications (CRUD), standardize route paths and component names to maintain predictability across teams:

Path Route Name Component Purpose
/users UsersIndex UsersIndex.vue List all users
/users/create UsersCreate UsersCreate.vue Form to create a user
/users/:id UsersShow UsersShow.vue Display user details
/users/:id/edit UsersEdit UsersEdit.vue Form to edit a user

Always navigate using named routes to decouple component logic from URL path changes:

<router-link :to="{ name: 'UsersIndex' }">Users</router-link>

Monorepos vs. Single App Architectures

When does a single application structure stop being enough?

  • Single Large Application: Stick with src/features/ modules and a shared/ directory.
  • Multiple Deployables (Admin + Client + Landing): Use workspace monorepos (pnpm or npm workspaces with Turborepo or Nx) to share UI libraries and utilities across distinct deployment builds without copy-pasting code.
  • Nuxt Applications: Consider Nuxt Layers as a lightweight alternative to full monorepo workspaces when extending application configurations across teams.

Frequently Asked Questions (FAQ)

What is the best folder structure for a large-scale Vue.js application?

Group by feature, not by file type. Create a src/features/ folder with one subfolder per domain (auth, billing, dashboard), and give each feature its own components, composables, store, API calls, and routes. Keep truly shared, domain-agnostic code in a separate shared/ folder. This keeps each feature self-contained and makes file placement predictable as the app grows.

Should I organize Vue files by type or by feature?

By type (components/, stores/, views/) is fine for a small app with one domain. Once you have more than one real domain, organize by feature, because grouping by type spreads a single feature across four or five folders and makes changes harder to reason about. A useful test: if editing one feature means touching files in many unrelated folders, switch to feature-based grouping.

When should I split a Vue app into feature modules?

Split when you have more than one distinct domain and the top-level components/ folder is getting hard to scan (roughly past 20 to 30 components). You do not need feature modules for a prototype. Migrate to them when the second real domain appears, and let each feature own everything it needs.

Do I need a monorepo for a large Vue.js project?

Only if you have more than one deployable app that shares code, for example a customer app and an admin app that use the same components and types. For a single large app, feature modules are enough. When you do need one, pnpm or npm workspaces cover most cases; add Turborepo or Nx when build caching and task orchestration start to matter.

Vuex or Pinia for state management in a large app?

Pinia. It is the current recommended state library for Vue, works cleanly with the Composition API and TypeScript, and drops the boilerplate Vuex required. In a feature-based structure, keep one Pinia store per feature inside that feature's folder, and reserve global stores for genuinely cross-cutting state like the current user or theme.

Conclusion

Structuring a large-scale Vue application is ultimately about predictability and developer experience. By adopting feature-based modules for domain code, reserving a shared/ui/ directory for base components, and leveraging modern Vue 3 tools like Vite, Pinia, and Composition API, your team can build applications that scale effortlessly.

Ready to level up your Vue 3 architecture? Master enterprise patterns in the flagship Vue.js 3 Master Class on Vue School.

What is the best folder structure for a large-scale Vue.js application?

Group by feature, not by file type. Create a src/features/ folder with one subfolder per domain (auth, billing, dashboard), and give each feature its own components, composables, store, API calls, and routes. Keep truly shared, domain-agnostic code in a separate shared/ folder. This keeps each feature self-contained and makes file placement predictable as the app grows.

Should I organize Vue files by type or by feature?

By type (components/, stores/, views/) is fine for a small app with one domain. Once you have more than one real domain, organize by feature, because grouping by type spreads a single feature across four or five folders and makes changes harder to reason about. A useful test: if editing one feature means touching files in many unrelated folders, switch to feature-based grouping.

When should I split a Vue app into feature modules?

Split when you have more than one distinct domain and the top-level components/ folder is getting hard to scan (roughly past 20 to 30 components). You do not need feature modules for a prototype. Migrate to them when the second real domain appears, and let each feature own everything it needs.

Do I need a monorepo for a large Vue.js project?

Only if you have more than one deployable app that shares code, for example a customer app and an admin app that use the same components and types. For a single large app, feature modules are enough. When you do need one, pnpm or npm workspaces cover most cases; add Turborepo or Nx when build caching and task orchestration start to matter.

Vuex or Pinia for state management in a large app?

Pinia. It is the current recommended state library for Vue, works cleanly with the Composition API and TypeScript, and drops the boilerplate Vuex required. In a feature-based structure, keep one Pinia store per feature inside that feature's folder, and reserve global stores for genuinely cross-cutting state like the current user or theme.

Start learning Vue.js for free

Comments

Latest Vue School Articles

5 Component Design Patterns to Boost Your Vue.js Applications

5 Component Design Patterns to Boost Your Vue.js Applications

5 essential Vue.js component design patterns, including branching components, slots usage, list organization, smart vs dumb components, and form handling - perfect for both Vue beginners and experienced developers looking to improve code maintainability and scalability.
Vibe Coding a Collaborative Editor with Comment Support with Nuxt UI and Jazz

Vibe Coding a Collaborative Editor with Comment Support with Nuxt UI and Jazz

Why I built a Nuxt + Jazz powered real time editor, how you can use it, and a list of takeaways on building with the help of AI.
VueSchool logo

Our goal is to be the number one source of Vue.js knowledge for all skill levels. We offer the knowledge of our industry leaders through awesome video courses for a ridiculously low price.

More than 200.000 users have already joined us. You are welcome too!

Follow us on Social