Consolidated guidance for structuring Vue 3 modular projects. Combines the repository conventions and the Vue3 modules approach into a single, actionable reference for design, enforcement, and migration.
This skill consolidates the web project conventions and the Vue3 project modules approach to help design, enforce, and migrate to and keep a modular Vue 3 architecture.
index.ts public API.components/, composables/, features/, entities/, and shared/ (UI kit + utilities).app/ (router, plugins, layouts, global styles).src/
├── app/ # router, plugins, layouts, styles
├── components/ # global business components
├── composables/ # global reusable composables
├── services/ # global API clients and services
├── entities/ # shared domain entities
├── stores/ # global Pinia stores
├── features/ # cross-cutting features (must have index.ts)
├── modules/ # domain modules (each must have index.ts)
└── shared/ # UI kit + pure utilities
Each src/modules/<feature>/ MUST follow this pattern. Only index.ts is public – everything else is private to the module.
src/modules/<feature>/
views/
components/
api/ # module-private API/business logic
features/ # module-local sub-features
types/
index.ts # PUBLIC API (MANDATORY)
Example index.ts public API:
// modules/auth/index.ts
export { default as moduleRoutes } from './routes'
export { default as moduleNavigation } from './menu'
export { Login } from './composables/login'
export type { AuthConfig } from './types'
Six layers (top → bottom). Layers may only import from layers below them.
app/): may import any lower layer but must access modules only via index.ts.modules/, features/): may import Shared Business, State, Data, Utility; no cross-module imports.composables/, components/, services/): can cross-reference each other and import lower layers.stores/): can import Data and Utility.entities/): can import Utility and other entities.shared/): self-contained (no imports from other layers).Use ESLint with eslint-plugin-vue-modular or equivalent rules to enforce these boundaries programmatically.
stores/ for shared state; module-local modules/*/stores/ for module-scoped state.Three robust solutions for registering module routes without violating layer rules:
Auto-discovery (recommended): use Vite's import.meta.glob to discover /src/modules/*/index.{ts,js} and read exported moduleRoutes.
Configuration-based: maintain an app/config/modules.ts listing enabled modules and load their public APIs dynamically with import().
Build-time generation: use a Vite plugin to scan modules and generate a generated-routes.ts file at build time.
Example (auto-discovery):
// app/router/auto-discovery.ts
const moduleApis = import.meta.glob('/src/modules/*/index.{ts,js}')
for (const path in moduleApis) {
const mod = await moduleApis[path]()
if (mod.moduleRoutes) routes.push(...mod.moduleRoutes)
}
PascalCase.PascalCase + View suffix (e.g., LoginView.vue).kebab-case.camelCase.PascalCase.<template> → <script> → <style> (style last)..test., .spec., tests/, __tests__/ are allowed to import internal module files for unit/integration testing.content to scan src/**/*.{vue,ts,js} so utilities are available across modules.shared/ui/.components/.features/ (must export index.ts).modules/<name>/.entities/.eslint-plugin-vue-modular (or your project linter config) to:
index.ts public APIs.Example ESLint enforcement snippet:
// eslint.config.mjs
import vueModular from 'eslint-plugin-vue-modular'
export default [
...vueModular.configs.recommended,
{
files: ['src/**/*.{ts,vue}'],
plugins: { 'vue-modular': vueModular },
settings: {
'vue-modular': { rootPath: 'src', rootAlias: '@', featuresPath: 'src/features', modulesPath: 'src/modules' }
}
}
]
index.ts public APIs for existing modules and migrate app imports to those APIs.recommended mode first; fix violations iteratively.composables/, features/, or shared/ rather than creating cross-module imports.index.ts public API and app imports use it.shared/ui/ and is used by business components.README.md and allow ESLint rule-level ignores where absolutely necessary.