логотип 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Использование Quasar Framework для разработки на Vue с готовыми UI-компонентамиСтрелочка вправо

Постройте личный план изучения Vue до уровня Middle — бесплатно!

Vue — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по Vue

Руководство по валидации форм во Vue.jsИнтеграция Tiptap для создания редакторов на VueРабота с таблицами во Vue через TanStackИнструкция по установке и компонентам Vue sliderУправление пакетами Vue js с помощью npmУправление пакетами и node modules в Vue проектахКак использовать meta для улучшения SEO на VueПолный гайд по компоненту messages во Vuejs5 правил использования Inertia с Vue и LaravelРабота с модулями и пакетами в VueИнструкция по работе с grid на VueGithub для Vue проектов - подробная инструкция по хранению и совместной работеНастройка ESLint для Vue проектов и поддержка качества кодаОбработка ошибок и отладка в Vue.jsИспользование Vue Devtools для отладки и мониторинга приложенийРабота с конфигурационными файлами и скриптами VueСоздание и настройка проектов Vue с помощью Vue CLI3 способа интеграции Chart.js с Vue для создания графиковРабота с Canvas во VueИнструкция по реализации календаря во VueРабота с Ant Design Vue для создания UI на Vue
Обзор и использование утилит Vue для удобной разработкиРабота с обновлениями компонента и жизненным циклом updateРазрешение конфликтов и ошибок с помощью Vue resolveИспользование query-параметров и их обработка в маршрутах VueЗагрузка и управление состоянием загрузки в VueИспользование библиотек Vue для расширения функционалаРабота с JSON данными в приложениях VueКак работать с экземплярами компонента Instance во VueПолучение данных и API-запросы во Vue.jsЭкспорт и импорт данных и компонентов в VueОбработка событий и их передача между компонентами VuejsГайд по defineEmits на Vue 3Понимание core функционала Vue и его применениеПонимание и применение Composition API в Vue 3Понимание и работа с компилятором VueКогда и как использовать $emit и call во VueВзаимодействие с внешними API через Axios в Vue
Веб приложения на Vue архитектура и лучшие практикиИспользование Vite для быстрого старта и сборки проектов на Vue 3Работа с URL и ссылками в приложениях на VueРабота с пользовательскими интерфейсами и UI библиотеками во VueОрганизация и структура исходных файлов в проектах VueИспользование Quasar Framework для разработки на Vue с готовыми UI-компонентамиОбзор популярных шаблонов и стартовых проектов на VueИнтеграция Vue с PHP для создания динамичных веб-приложенийКак организовать страницы и маршруты в проекте на VueNuxt JS и Vue 3 для SSR приложенийСоздание серверных приложений на Vue с помощью Nuxt jsИспользование Vue Native для разработки мобильных приложенийОрганизация и управление индексной страницей в проектах VueИспользование Docker для контейнеризации приложений на VueИнтеграция Vue.js с Django для создания полноценных веб-приложенийСоздание и работа с дистрибутивом build dist Vue приложенийРабота со стилями и CSS в Vue js для красивых интерфейсовСоздание и структурирование Vue.js приложенияКак исправить ошибку cannot find module vueНастройка и сборка проектов Vue с использованием современных инструментовИнтеграция Vue с Bitrix для корпоративных решенийРазработка административных панелей на Vue js
5 библиотек для создания tree view во VueИнтеграция Tailwind CSS с Vue для современных интерфейсовИнтеграция Vue с серверной частью и HTTPS настройкамиКак обрабатывать async операции с Promise во VueИнтеграция Node.js и Vue.js для разработки приложенийРуководство по интеграции Vue js в NET проектыПримеры использования JSX во VueГайд по импорту и регистрации компонентов на VueМногоязычные приложения на Vue с i18nИнтеграция FLIR данных с Vue5 примеров использования filter во Vue для упрощения разработки3 примера реализации drag-and-drop во Vue
Управление переменными и реактивными свойствами во VueИспользование v for и slot в VueПрименение v-bind для динамической привязки атрибутов в VueУправление пользователями и их данными в Vue приложенияхСоздание и использование UI Kit для Vue приложенийТипизация и использование TypeScript в VuejsИспользование шаблонов в Vue js для построения интерфейсовИспользование Swiper для создания слайдеров в VueРабота со стилями и стилизацией в VueСтруктура и особенности Single File Components SFC в VueРабота со SCSS в проектах на Vue для стилизацииРабота со скроллингом и прокруткой в Vue приложенияхПрименение script setup синтаксиса в Vue 3 для упрощения компонентовИспользование scoped стилей для изоляции CSS в компонентах Vue3 способа улучшить навигацию Vue с push()Обработка запросов и асинхронных операций в VueПонимание и использование provide inject для передачи данных между компонентамиПередача и использование props в Vue 3 для взаимодействия компонентовПередача данных между компонентами с помощью props в Vue jsУправление property и функциями во Vue.jsРабота со свойствами компонентов VueУправление параметрами и динамическими данными во VueРабота с lifecycle-хуком onMounted во VueОсновы работы с объектами в VueПонимание жизненного цикла компонента Vue js на примере mountedИспользование модальных окон modal в Vue приложенияхИспользование методов в компонентах Vue для обработки логикиИспользование метода map в Vue для обработки массивовИспользование хуков жизненного цикла Vue для управления состоянием компонентаРабота с ключами key в списках и компонентах VueОбработка пользовательского ввода в Vue.jsРабота с изображениями и их оптимизация в VueИспользование хуков жизненного цикла в VueОрганизация сеток и гридов для верстки интерфейсов на VueСоздание и управление формами в VueОрганизация файлов и структура проекта Vue.jsКомпоненты Vue создание передача данных события и emitРабота с динамическими компонентами и данными в Vue3 способа манипулирования DOM на VueРуководство по div во VueИспользование директив в Vue и их расширенные возможностиОсновы и применение директив в VueИспользование директив и их особенности на Vue с помощью defineИспользование компонентов datepicker в Vue для выбора датОрганизация циклов и итераций во VueКак работает компиляция Vue CoreСоздание и использование компонентов в Vue JSОбработка кликов и пользовательских событий в VueИспользование классов в Vue для организации кода и компонентовИспользование директивы checked для управления состоянием чекбоксов в VueГайд на checkbox компонент во VueОтображение данных в виде графиков с помощью Vue ChartСоздание и настройка кнопок в VueСоздание и настройка кнопок в Vue приложенияхРабота с lifecycle-хуками beforeCreate и beforeMount во VueИспользование массивов и методов их обработки в VueИспользование массивов и их обработка в Vue
Использование Vuetify для создания современных интерфейсов на VueИспользование transition во VueТестирование компонентов и приложений на VueРабота с teleport для управления DOM во VueПять шагов по настройке SSR в VuejsИспользование Shadcn UI компонентов с Vue для продвинутых интерфейсовИспользование router-link для навигации в Vue RouterКак использовать require в Vue для динамического импорта модулейРабота с динамическим рендерингом и виртуальным DOM на Vue.jsИспользование ref для управления ссылками и реактивностью в Vue 3Использование Vue Pro и его преимущества для профессиональной разработкиРуководство по nextTick для работы с DOMСоздание и использование компонентов с помощью Vue js и CУправление состоянием и реактивностью через inject и provideДинамическое обновление компонентов и данных на VueГлубокое изучение документации Vue и как эффективно её использоватьИспользование Crystal с Vue для разработкиИспользование вычисляемых свойств для динамического отображения данных на Vue jsОптимизация производительности и предупреждения в Vue
Открыть базу знаний

Лучшие курсы по теме

изображение курса

Vue 3 и Pinia

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.9
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий