логотип PurpleSchool
логотип PurpleSchool

Организация и структура исходных файлов в проектах Vue

Автор

Олег Марков

Введение

При работе над приложениями на Vue очень важно с самого начала продумывать структуру исходных файлов. Правильная организация каталогов и файлов ― это не только порядок в проекте, но и залог удобной поддержки, масштабирования и быстрой адаптации новых участников команды. Ниже мы разберемся, какие подходы используют разработчики на практике, как организовать свой проект для решения самых разных задач, какие файлы и папки будут в нем встречаться и почему имеет смысл разделять одни сущности, а другие объединять. Я покажу на примерах, как устроены реальные проекты, и объясню, для чего нужны разные уровни вложенности.

Стартовая структура Vue-проекта

Сначала давайте рассмотрим базовую структуру Vue-приложения, которая получается при инициализации через Vue CLI (vue create my-app) или Vite (npm init vue@latest). Посмотрим, что там появляется автоматически:

my-app/
├─ node_modules/
├─ public/
│  └─ index.html
├─ src/
│  ├─ assets/
│  ├─ components/
│  ├─ App.vue
│  └─ main.js
├─ .gitignore
├─ package.json
└─ README.md

Объясню коротко, зачем нужны эти папки и файлы:

  • node_modules/ — сторонние пакеты, сюда обычно не лезут руками.
  • public/ — статические файлы (иконки, favicon, HTML-шаблон).
  • src/ — тут лежит весь исходный код приложения.
  • .gitignore — список файлов и папок, игнорируемых git.
  • package.json — зависимости, скрипты и базовые настройки проекта.
  • README.md — описание проекта для разработчиков.

Дальнейшая организация всей логики и компонентов будет происходить именно внутри папки src.


Разделение по слоям и каталоги в папке src

Базовые каталоги

В типовом проекте на Vue структура файлов немного напоминает анатомию: каждая группа файлов — это отдельная “система”, отвечающая за свои функции:

src/
├─ assets/       // Статические ресурсы — картинки, шрифты
├─ components/   // Мелкие переиспользуемые компоненты-инструменты
├─ views/        // Страницы/экраны, которые сопоставляются с роутами
├─ router/       // Настройки маршрутизации
├─ store/        // Центральное хранилище состояния (Vuex, Pinia)
├─ composables/  // Логика-композиции (composition API)
├─ utils/        // Утилиты и хелперы
├─ services/     // Работа с API/сервером
├─ App.vue       // Главный корневой компонент
└─ main.js       // Точка входа, инициализация Vue-приложения

Теперь коротко о каждом разделе.

assets

Тут хранят изображения, иконки, шрифты, иногда глобальные стили (например, normalize.css), которые нужны во всем проекте.

components

Сюда складываются переиспользуемые, независимо автономные “кусочки” интерфейса: кнопки, карточки, поля ввода, модальные окна и т.д.

views

Подходит для компонентов-страниц. Например, "Главная", "Профиль пользователя", "Панель администратора" — всё, что напрямую сопоставляется какому-либо маршруту в приложении. Обычно каждая view состоит из других компонентов.

router

Обычно содержит основной файл маршрутизации (router.js или index.js), где описаны маршруты, хуки, middleware и т.п.

store

Если вы используете Vuex или Pinia, здесь будут лежать модули состояния, actions, mutations, getters и т.д.

composables

Здесь складываются функции-композиции. Это отличный паттерн для выделения повторяющейся бизнес-логики. Подробнее расскажу чуть ниже.

utils

Мелкие утилитарные функции, которые не зависят от состояния приложения или Vue и могут использоваться где угодно.

services

Инкапсулирует работу с API: здесь лежат функции для HTTP-запросов (fetch/axios).

Пример более развернутой структуры

Если проект становится больше, появляются доп. уровни вложенности:

src/
├─ components/
│  ├─ ui/               // Общие элементы: Button, Modal, Icon
│  └─ layout/           // Header, Footer, Sidebar и др.
├─ views/
│  ├─ HomeView.vue
│  ├─ ProfileView.vue
│  └─ SettingsView.vue
├─ store/
│  ├─ modules/
│  │  ├─ user.js
│  │  └─ products.js
│  └─ index.js
├─ composables/
│  ├─ useAuth.js
│  └─ useFetch.js
├─ utils/
├─ services/
├─ router/
├─ App.vue
└─ main.js

Выделение папки ui особенно удобно, если собирается “собственная библиотека” часто используемых элементов управления. Папка layout содержит некие “шаблоны” с местами для вложения содержимого.


Организация компонентов

Что класть в components/

В папке components размещаются, как правило, атомарные (Button, Input), молекулярные (Form, Card) и иногда ещё более сложные компоненты, из которых собираются страницы. Переиспользуемость—главный ориентир.

Давайте рассмотрим структуру примера:

src/
├─ components/
│  ├─ ui/
│  │  ├─ MyButton.vue
│  │  └─ MyInput.vue
│  ├─ layout/
│  │  ├─ AppHeader.vue
│  │  ├─ AppFooter.vue
│  │  └─ AppSidebar.vue
│  └─ MyCard.vue

Пример компонента MyButton.vue

<template>
  <button :class="['my-button', type]" @click="$emit('click')">
    <slot />
  </button>
</template>

<script>
export default {
  name: 'MyButton',
  props: {
    type: {
      type: String,
      default: 'primary' // primary, secondary
    }
  }
}
</script>

// Компонент — простая кнопка с предустановленным стилем, можно использовать везде.

Когда создавать отдельную папку для компонента

Если компонент сложный и требует собственных стилей, вспомогательных функций или тестов, стоит выделить под него отдельную папку. Например:

components/
└─ UserCard/
   ├─ UserCard.vue
   ├─ UserAvatar.vue
   ├─ UserCard.spec.js  // тесты
   ├─ styles.css
   └─ utils.js

Это удобно для изоляции логики и упрощает навигацию по коду.


Page-компоненты: Каталог views

Папка views нужна для видимых пользователю страниц, часто связанных с роутами. Поддерживать единый шаблон легко:

views/
├─ HomeView.vue
├─ ProfileView.vue
└─ ArticleView.vue

Каждая страница обычно собирается из компонентов разного уровня вложенности, отображая одну бизнес-функцию.

Пример страницы:

<template>
  <div>
    <AppHeader />
    <UserCard :user="user" />
    <AppFooter />
  </div>
</template>

<script>
import UserCard from '@/components/UserCard/UserCard.vue'
export default {
  name: 'ProfileView',
  components: { UserCard },
  data() {
    return {
      user: {
        name: "Иван",
        avatar: "/img/ivan.png"
      }
    }
  }
}
</script>

Как видите, страницы сами «складываются» из компонентов.


Работа с Vue Router: каталог router

В небольших проектах настройки роутера обычно помещают в одном файле:

src/router/index.js

Здесь указываются пути страниц, lazy load, guards и т. д.

// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '@/views/HomeView.vue'
import ProfileView from '@/views/ProfileView.vue'

const routes = [
  { path: '/', name: 'home', component: HomeView },
  { path: '/profile', name: 'profile', component: ProfileView }
]

export default createRouter({
  history: createWebHistory(),
  routes
})

Если приложение большое, конфигурацию роутов делят по модулям (например, admin.js, user.js).


Организация глобального состояния: папка store

Vuex или Pinia помогают централизовать логику работы с состоянием приложения. Обычно структура такая:

store/
├─ modules/
│  ├─ user.js
│  └─ products.js
├─ index.js

index.js подключает модули и экспортирует настроенный store для всего приложения.

Пример модуля user.js:

// store/modules/user.js
export default {
  state: () => ({
    loggedIn: false,
    userInfo: null
  }),
  mutations: {
    login(state, userInfo) {
      state.loggedIn = true
      state.userInfo = userInfo
    },
    logout(state) {
      state.loggedIn = false
      state.userInfo = null
    }
  }
}

Такой подход помогает держать логику “по темам”, не сливая всё в один файл.


composables: Как разбивать логику по use-функциям

Папка composables появилась с приходом Composition API (Vue 3). Как её использовать — покажу на полезном примере.

Допустим, в разных частях приложения вам нужна функция для работы с локальным хранилищем:

// composables/useLocalStorage.js
import { ref, watch } from 'vue'

export function useLocalStorage(key, initialValue) {
  const storedValue = ref(localStorage.getItem(key) || initialValue)

  // обновлять localStorage при изменении значения
  watch(storedValue, (newValue) => {
    localStorage.setItem(key, newValue)
  })

  return storedValue
}

Использование в компоненте:

import { useLocalStorage } from '@/composables/useLocalStorage'

export default {
  setup() {
    const name = useLocalStorage('name', 'Гость')
    return { name }
  }
}

В composables/ удобно выносить любую бизнес-логику, которую можно, как “крючок”, подтягивать в разные части приложения: работа с API, подписка на события, анимации, склонение слов и так далее.


utils: папка для хелперов

В utils/ удобно держать любые js-функции, не связанные с состоянием, компонентами и Vue:

// utils/pluralize.js
export function pluralize(value, one, two, five) {
  // 1 элемент, 2 элемента, 5 элементов — функция склонения числительных
  // Пример: pluralize(2, 'товар', 'товара', 'товаров') => 'товара'
}

services: как организовать работу с внешними API

Все запросы к серверу лучше вынести в отдельную папку services/. Например:

// services/userService.js
import axios from 'axios'

export function fetchUser(userId) {
  return axios.get(`/api/users/${userId}`)
}

export function updateUser(userId, data) {
  return axios.put(`/api/users/${userId}`, data)
}

Дальше эти функции можно подключать в composables или напрямую в компоненты.


Переход к масштабируемой архитектуре: Feature-based structure

Для больших проектов структура “по слоям” иногда осложняет работу из-за большого числа компонентов и файлов. В таких случаях применяют “feature-based structure”: разделение по функциональным областям.

src/
├─ features/
│  ├─ auth/
│  │  ├─ components/
│  │  ├─ views/
│  │  ├─ composables/
│  │  ├─ store/
│  │  └─ services/
│  ├─ profile/
│  └─ catalog/
├─ shared/
│  ├─ components/
│  ├─ utils/
│  └─ composables/
├─ App.vue
├─ main.js

“Особенности” (features) — модули или секции приложения, каждый со своими компонентами, стором, сервисами. В папку shared/ кладут то, что используется массово по всему приложению.


Импорт путей и абсолютные алиасы

В больших проектах удобно использовать алиасы, чтобы не писать постоянно длинные относительные пути. Обычно в vite.config.js или vue.config.js настраивают:

// vite.config.js
import { fileURLToPath, URL } from 'url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})

Теперь @/components/MyButton.vue всегда отсылает к src/components/MyButton.vue. Это облегчает навигацию и рефакторинг.


Принципы, которые стоит соблюдать при организации проекта

  • Структура должна расти вместе с приложением. Делайте минимально необходимую вложенность, избегайте пустых папок.
  • Изолируйте бизнес-логику по областям: повторяющийся функционал выносите в composables или utils.
  • Даже в небольших проектах старайтесь придерживаться единого стиля именования.
  • Разделяйте внешний и внутренний код: мок-данные, тесты, прототипы удобно держать в отдельных директориях.
  • Документируйте проект: что лежит в какой папке, как запускать, как добавлять новые фичи.

Более сложные примеры реальных проектов

Для полноты картины часто помогают реальные схемы, применяемые на крупных проектах:

Пример большого проекта (итеративно расширяемой структуры):

src/
├─ api/            // все сервисы и клиенты (GraphQL, REST)
├─ assets/         // иконки, лого, css, шрифты
├─ components/     // UI- и базовые компоненты
├─ config/         // конфигурационные файлы (переменные, настройки)
├─ constants/      // константы для повторного использования
├─ directives/     // vue-директивы
├─ features/       // фичи и хранилища по областям
├─ hooks/          // композиционные хуки
├─ layouts/        // шаблоны страниц
├─ locales/        // локализация
├─ mixins/         // миксины, если используется Options API
├─ router/         // роутер со структурой
├─ store/          // Vuex/Pinia
├─ styles/         // SCSS/Less и переменные
├─ types/          // определения типов, если используется TypeScript
├─ utils/          // хелперы
├─ views/          // основные страницы
└─ main.ts         // точка входа

Такой подход облегчает параллельную разработку, ведение документации и, главное, упрощает масштабирование.


Заключение

Грамотная организация исходных файлов — ключ к гибкости, поддерживаемости и расширяемости вашего Vue-приложения. Не существует универсального шаблона: структура должна исходить из задач, которые вы решаете, и со временем “взрослеть” вместе с проектом. Правильное разделение по слоям и функциональным областям помогает быстрее находить нужный код, тестировать и добавлять новые функции без слома существующей логики.


Частозадаваемые технические вопросы по теме статьи и ответы на них

Как подключать SCSS или глобальные стили к компонентам?

Для глобальных стилей — положите их в src/assets/styles/, подключайте один раз в main.js (или App.vue).
Пример для main.js:
js import '@/assets/styles/main.scss' Для SCSS-модулей в отдельных компонентах используйте <style lang="scss" scoped>. Если нужно подключить общие SCSS-переменные во все компоненты, настройте соответствующий loader (vite.config.js, vue.config.js) — секция css.preprocessorOptions.

Как структурировать проект с Vue Router при большом количестве маршрутов?

Разбейте конфигурацию роутов на модули.
Пример: создайте папки router/modules/, куда разместите файлы admin.js, user.js, catalog.js; в router/index.js импортируйте их и объединяйте в один массив routes.

Можно ли совмещать Option API и Composition API в одном проекте/компоненте?

Да, можно. Например, вы можете использовать <script setup> вместе с обычными опциями — они будут работать совместно. Постепенно переносите старые компоненты на Composition API, если возникает необходимость.

Как корректно структурировать тесты для компонентов Vue?

Лучше всего держать тесты в соседних папках или как соседние файлы с компонентами.
Пример: components/MyButton/MyButton.spec.js или __tests__/MyButton.spec.js.

Что делать, если хочется использовать TypeScript в Vue-проекте?

При создании проекта через Vue CLI или Vite можно выбрать TypeScript.
Все компоненты тогда используют расширение .ts (или .vue c <script lang="ts">).
Выделите отдельную папку src/types/ для глобальных типов, описывайте props и объекты через интерфейсы или типы.
Настройте main.ts аналогично main.js, только заменив синтаксис на TypeScript.

Стрелочка влевоРабота с пользовательскими интерфейсами и UI библиотеками во VueОбзор популярных шаблонов и стартовых проектов на VueСтрелочка вправо

Все гайды по Vue

Работа с пользовательскими интерфейсами и UI библиотеками во VueОрганизация и структура исходных файлов в проектах VueОбзор популярных шаблонов и стартовых проектов на VueКак организовать страницы и маршруты в проекте на VueСоздание серверных приложений на Vue с помощью Nuxt jsРабота со стилями и CSS в Vue js для красивых интерфейсовСоздание и структурирование Vue.js приложенияНастройка и сборка проектов Vue с использованием современных инструментов
Управление переменными и реактивными свойствами во VueИспользование v for и slot в VueТипизация и использование TypeScript в VuejsИспользование шаблонов в Vue js для построения интерфейсовПередача данных между компонентами с помощью props в Vue jsУправление property и функциями во Vue.jsОсновы работы с объектами в VueПонимание жизненного цикла компонента Vue js на примере mountedИспользование метода map в Vue для обработки массивовОбработка пользовательского ввода в Vue.jsОрганизация файлов и структура проекта Vue.jsКомпоненты Vue создание передача данных события и emitИспользование директив и их особенности на Vue с помощью defineСоздание и использование компонентов в Vue JSОбработка кликов и пользовательских событий в Vue
Открыть базу знаний