T-CREATOR

Vue.js プロジェクトのディレクトリ設計ベストプラクティス

Vue.js プロジェクトのディレクトリ設計ベストプラクティス

Vue.jsでWebアプリケーションを開発する際、多くの開発者が直面するのがディレクトリ構造の設計です。プロジェクト規模が大きくなるにつれて、ファイルの配置や組織化に悩むことはありませんか。

効率的な開発とメンテナンス性の高いコードを実現するためには、最初から適切なディレクトリ構造を設計することが重要になります。本記事では、小〜中規模のVue.jsプロジェクトに適した基本的なディレクトリ構造のベストプラクティスについて詳しく解説いたします。

実際のプロジェクト例とともに、保守性・拡張性・チーム開発効率を向上させるディレクトリ設計の考え方をお伝えしていきますね。

背景

Vue.js プロジェクトの複雑さが増す課題

Vue.jsプロジェクトは、開発が進むにつれて急激に複雑性が増していきます。最初は数個のコンポーネントで始まったプロジェクトも、機能追加やユーザビリティ向上のために、あっという間に数十、数百のファイルを抱える大きな規模になるでしょう。

この成長過程で、多くの開発者が経験するのがファイル管理の困難さです。「あのコンポーネントはどこに配置したっけ」「この機能に関連するファイルはどこにある」といった状況に陥ります。

複雑さが増す主な要因として以下のポイントが挙げられます。

下図は、Vue.jsプロジェクトの成長パターンを表現したものです。

mermaidflowchart TD
    start[小規模プロジェクト開始] --> comp1[基本コンポーネント追加]
    comp1 --> pages[ページ機能追加]
    pages --> api[API連携実装]
    api --> state[状態管理導入]
    state --> utils[共通機能追加]
    utils --> complex[複雑な依存関係]
    complex --> maintenance[保守困難]
    
    style start fill:#e1f5fe
    style maintenance fill:#ffebee
    style complex fill:#fff3e0

プロジェクトの初期段階では単純だった構造が、機能追加とともに複雑化し、最終的に保守が困難な状態に陥るパターンです。

効率的な開発を実現するディレクトリ設計の重要性

適切なディレクトリ設計は、開発効率を大幅に向上させます。チームメンバーが直感的にファイルを見つけられる構造は、新機能の実装速度を向上させるでしょう。

また、明確な役割分担に基づいたディレクトリ構造は、コードレビューの質を高め、バグの早期発見にもつながります。ファイルの配置ルールが統一されていれば、どの開発者でも一貫した品質でコードを書けますね。

効率的な開発を支える設計の要素は以下のとおりです。

#要素効果具体的メリット
1役割の明確化迷いの軽減ファイル配置判断の高速化
2依存関係の可視化影響範囲の把握リファクタリング時の安全性向上
3共通機能の集約重複コード削減開発・保守コスト削減
4規約の統一品質の均一化チーム開発での生産性向上

特に重要なのは、プロジェクトの成長に対応できる拡張性を持った設計です。後から大幅な構造変更が必要になると、開発コストが大きくなってしまいます。

課題

よくあるディレクトリ設計の問題点

Vue.jsプロジェクトでよく見られる問題のあるディレクトリ構造をご紹介します。これらのパターンは短期的には機能しますが、長期的な保守性に大きな課題をもたらすでしょう。

すべてを一箇所に配置する問題

javascript// 問題のある例:すべてのコンポーネントを一つのフォルダに配置
src/
├── components/
│   ├── Header.vue
│   ├── Footer.vue
│   ├── UserProfile.vue
│   ├── ProductCard.vue
│   ├── ShoppingCart.vue
│   └── OrderForm.vue  // 30個以上のファイルが並ぶ状態

この構造では、ファイル数が増えるにつれて目的のコンポーネントを見つけることが困難になります。

機能と技術の混在

javascript// 問題のある例:機能と技術要素が混在した配置
src/
├── views/
├── components/
├── api/
├── user-utils/     // ユーザー機能のユーティリティ
├── product-api/    // 商品機能のAPI
└── cart-store/     // カート機能のストア

この例では、同じ機能に関連するファイルが複数のディレクトリに分散してしまっています。

保守性を損なう構造パターン

保守性を損なう代表的なパターンをいくつか見ていきましょう。これらは初期開発では問題になりませんが、プロジェクトが成長すると大きな障害となります。

深すぎる階層構造

javascript// 問題のある例:過度に深いネスト
src/
├── components/
│   ├── pages/
│   │   ├── user/
│   │   │   ├── profile/
│   │   │   │   ├── details/
│   │   │   │   │   ├── personal/
│   │   │   │   │   │   └── BasicInfo.vue  // 6階層の深さ

このような深い階層は、ファイルへのアクセスを困難にし、import文が非常に長くなってしまいます。

命名規則の不統一

javascript// 問題のある例:命名規則がバラバラ
src/
├── components/
│   ├── userProfile.vue      // キャメルケース
│   ├── product-card.vue     // ケバブケース
│   ├── ShoppingCart.vue     // パスカルケース
│   └── order_form.vue       // スネークケース

統一されていない命名規則は、ファイル検索の効率を下げ、チーム内での混乱を招きます。

チーム開発での混乱を招く要因

チーム開発において、ディレクトリ設計の問題は個人開発以上に深刻な影響をもたらします。複数の開発者が同時に作業する環境では、明確なルールとガイドラインが必要です。

ファイル配置の判断基準が不明確

新しいコンポーネントを作成する際、どのディレクトリに配置すべきかが分からない状況は頻繁に発生します。判断基準が曖昧だと、同じような機能のファイルが異なる場所に配置され、プロジェクト全体の一貫性が失われるでしょう。

依存関係の把握困難

ファイル間の依存関係が不明確だと、修正時の影響範囲を把握することが困難になります。一つのファイルを変更した際に、どのファイルに影響が及ぶか分からない状態は、バグの原因となりやすいです。

新メンバーのオンボーディング負荷

プロジェクトに新しく参加したメンバーが、ディレクトリ構造を理解するまでに時間がかかると、チーム全体の生産性に影響します。直感的で理解しやすい構造が求められますね。

解決策

Vue.js プロジェクトの理想的なディレクトリ構造

効果的なVue.jsプロジェクトのディレクトリ構造は、以下の原則に基づいて設計されています。機能ごとの明確な分離と、プロジェクトの成長に対応できる拡張性を重視した構造をご紹介します。

基本的なディレクトリ構造

javascriptsrc/
├── assets/           // 静的ファイル
├── components/       // 再利用可能なコンポーネント
├── composables/      // Composition API関数
├── layouts/          // レイアウトコンポーネント  
├── pages/           // ページコンポーネント
├── plugins/         // Vueプラグイン
├── stores/          // 状態管理
├── types/           // TypeScript型定義
├── utils/           // ユーティリティ関数
└── App.vue

この構造では、各ディレクトリが明確な役割を持っており、開発者が迷わずにファイルを配置できます。

機能別サブディレクトリの活用

大きなディレクトリ内では、機能別にサブディレクトリを作成して整理しましょう。

javascriptcomponents/
├── ui/              // 基本的なUIコンポーネント
│   ├── Button/
│   ├── Input/
│   └── Modal/
├── forms/           // フォーム関連
│   ├── LoginForm/
│   ├── RegisterForm/
│   └── ContactForm/
└── features/        // 機能固有のコンポーネント
    ├── UserProfile/
    ├── ProductCard/
    └── ShoppingCart/

このように分類することで、コンポーネントの役割が明確になり、再利用しやすくなります。

下図は、理想的なディレクトリ構造の依存関係を示したものです。

mermaidflowchart TD
    pages[Pages] --> layouts[Layouts]
    pages --> features[Feature Components]
    features --> ui[UI Components]
    layouts --> ui
    features --> composables[Composables]
    features --> stores[Stores]
    composables --> utils[Utils]
    stores --> utils
    
    style pages fill:#e3f2fd
    style ui fill:#f3e5f5
    style composables fill:#e8f5e8
    style stores fill:#fff3e0

上位層から下位層への一方向の依存関係を保つことで、循環参照を防ぎ、保守しやすい構造を実現できます。

役割ごとの明確な分離

各ディレクトリの役割を明確に定義することで、ファイル配置の判断を迅速かつ正確に行えるようになります。以下に各ディレクトリの詳細な役割をご説明いたします。

assetsディレクトリ

javascriptassets/
├── images/
│   ├── icons/
│   ├── logos/
│   └── backgrounds/
├── styles/
│   ├── variables.scss
│   ├── mixins.scss
│   └── global.scss
└── fonts/
    └── custom-fonts.woff2

assetsには、画像、スタイルシート、フォントなどの静的ファイルを配置します。サブディレクトリで種類別に整理しましょう。

componentsディレクトリの階層化

javascriptcomponents/
├── ui/              // 基本UIコンポーネント(再利用性高)
├── forms/           // フォーム専用コンポーネント
├── features/        // 機能特化コンポーネント
└── layout/          // レイアウト補助コンポーネント

コンポーネントを用途と再利用性で分類することで、適切なコンポーネントを素早く見つけられます。

composablesディレクトリの活用

javascriptcomposables/
├── useAuth.js       // 認証関連ロジック
├── useApi.js        // API通信ロジック
├── useValidation.js // バリデーションロジック
└── useLocalStorage.js // ローカルストレージ操作

Composition APIの関数は、機能ごとに分離して配置します。これにより、ロジックの再利用性が向上するでしょう。

スケーラブルな設計原則

プロジェクトの成長に対応できるスケーラブルな設計を実現するための原則をご紹介します。これらの原則に従うことで、長期的なメンテナンス性を確保できます。

単一責任の原則

各ディレクトリとファイルは、一つの明確な責任のみを持つべきです。複数の責任を持つファイルは、分割を検討しましょう。

javascript// 良い例:単一責任
composables/
├── useUserAuth.js      // ユーザー認証のみ
├── useUserProfile.js   // ユーザープロフィールのみ
└── useUserSettings.js  // ユーザー設定のみ

// 悪い例:複数責任
composables/
└── useUser.js         // 認証・プロフィール・設定が混在

依存関係の方向制御

上位層は下位層に依存できますが、その逆は避けるべきです。この原則により、循環依存を防げます。

javascript// 良い例:一方向依存
pages/UserPage.vue → components/features/UserProfile.vue → components/ui/Button.vue

// 悪い例:循環依存
components/ui/Button.vue ↔ components/features/UserProfile.vue

設定ファイルの集約

プロジェクト設定は一箇所に集約し、変更の影響範囲を限定しましょう。

javascriptsrc/
├── config/
│   ├── api.js          // API設定
│   ├── constants.js    // 定数
│   └── environment.js  // 環境設定

設定を集約することで、環境別の設定変更や定数の管理が容易になります。

具体例

基本的なVue.jsプロジェクトの構造例

実際のプロジェクトを想定して、具体的なディレクトリ構造とファイル配置例をご紹介します。ECサイトのような中規模アプリケーションを例に見ていきましょう。

プロジェクト全体の構造

javascriptmy-vue-project/
├── public/
│   ├── favicon.ico
│   └── index.html
├── src/
│   ├── assets/
│   ├── components/
│   ├── composables/
│   ├── layouts/
│   ├── pages/
│   ├── plugins/
│   ├── router/
│   ├── stores/
│   ├── types/
│   ├── utils/
│   ├── App.vue
│   └── main.js
├── package.json
└── vite.config.js

この構造は、Vue 3 + Viteの標準的なプロジェクト構成をベースにしています。

各ディレクトリの詳細配置例

まず、assetsディレクトリの構造から見ていきましょう。

javascriptassets/
├── images/
│   ├── icons/
│   │   ├── cart.svg
│   │   ├── user.svg
│   │   └── search.svg
│   ├── products/
│   │   ├── sample-product.jpg
│   │   └── placeholder.png
│   └── common/
│       ├── logo.png
│       └── background.jpg
├── styles/
│   ├── variables.scss
│   ├── mixins.scss
│   ├── reset.scss
│   └── global.scss
└── fonts/
    └── Noto-Sans-JP.woff2

画像は用途別にディレクトリを分け、スタイルファイルは役割別に整理しています。

下図は、ECサイトプロジェクトの構成要素の関係を示したものです。

mermaidflowchart TD
    app[App.vue] --> router[Vue Router]
    router --> pages[Pages]
    pages --> layouts[Layouts]
    pages --> features[Feature Components]
    
    features --> ui[UI Components]
    features --> composables[Composables]
    features --> stores[Pinia Stores]
    
    composables --> utils[Utils]
    stores --> api[API Utils]
    
    subgraph "Pages Layer"
        homepage[Home Page]
        productpage[Product Page]
        cartpage[Cart Page]
    end
    
    subgraph "Feature Components"
        productcard[Product Card]
        cartitem[Cart Item]
        userprofile[User Profile]
    end
    
    style app fill:#e1f5fe
    style pages fill:#e8f5e8
    style features fill:#fff3e0
    style ui fill:#f3e5f5

各層が明確な役割を持ち、一方向の依存関係を維持していることが分かります。

コンポーネント配置のベストプラクティス

コンポーネントの配置は、再利用性と保守性を考慮して決定する必要があります。具体的な配置例とガイドラインをご紹介いたします。

UIコンポーネントの配置

基本的なUIコンポーネントは、以下のように配置しましょう。

javascriptcomponents/ui/
├── Button/
│   ├── Button.vue
│   ├── Button.stories.js  // Storybookストーリー
│   └── index.js          // エクスポート用
├── Input/
│   ├── Input.vue
│   ├── Input.stories.js
│   └── index.js
└── Modal/
    ├── Modal.vue
    ├── ModalHeader.vue
    ├── ModalBody.vue
    ├── ModalFooter.vue
    └── index.js

各コンポーネントを独立したディレクトリに配置し、関連ファイルをまとめます。

Buttonコンポーネントの実装例

vue<!-- components/ui/Button/Button.vue -->
<template>
  <button
    :class="buttonClasses"
    :disabled="disabled"
    @click="handleClick"
  >
    <span v-if="loading" class="loading-spinner"></span>
    <slot v-else></slot>
  </button>
</template>

<script setup>
import { computed } from 'vue'

// プロパティの定義
const props = defineProps({
  variant: {
    type: String,
    default: 'primary',
    validator: (value) => ['primary', 'secondary', 'danger'].includes(value)
  },
  size: {
    type: String,
    default: 'medium',
    validator: (value) => ['small', 'medium', 'large'].includes(value)
  },
  disabled: {
    type: Boolean,
    default: false
  },
  loading: {
    type: Boolean,
    default: false
  }
})
</script>

プロパティのバリデーションと型チェックを含めることで、コンポーネントの品質を保っています。

エクスポート用indexファイル

javascript// components/ui/Button/index.js
export { default } from './Button.vue'

// 複数のコンポーネントをエクスポートする場合
// export { default as Button } from './Button.vue'
// export { default as IconButton } from './IconButton.vue'

indexファイルを使用することで、インポート文を簡潔にできます。

機能特化コンポーネントの配置

特定の機能に特化したコンポーネントは、featuresディレクトリに配置しましょう。

javascriptcomponents/features/
├── ProductCard/
│   ├── ProductCard.vue
│   ├── ProductImage.vue
│   ├── ProductInfo.vue
│   └── ProductActions.vue
├── ShoppingCart/
│   ├── CartContainer.vue
│   ├── CartItem.vue
│   └── CartSummary.vue
└── UserProfile/
    ├── ProfileHeader.vue
    ├── ProfileForm.vue
    └── ProfileSettings.vue

関連するコンポーネントをグループ化することで、機能の境界が明確になります。

アセット管理の最適化

アセットファイルの効率的な管理は、プロジェクトの保守性と読み込み性能に直結します。適切な配置と命名規則により、開発効率を向上させましょう。

画像ファイルの分類と命名

javascriptassets/images/
├── icons/
│   ├── ui/
│   │   ├── arrow-right.svg
│   │   ├── close-button.svg
│   │   └── loading-spinner.svg
│   ├── social/
│   │   ├── facebook-icon.svg
│   │   ├── twitter-icon.svg
│   │   └── instagram-icon.svg
│   └── features/
│       ├── cart-icon.svg
│       ├── user-icon.svg
│       └── search-icon.svg
├── products/
│   ├── thumbnails/
│   └── full-size/
└── branding/
    ├── logo-light.png
    ├── logo-dark.png
    └── favicon.ico

用途別の分類と一貫した命名規則により、目的の画像を素早く見つけられます。

スタイルファイルの構成

javascriptassets/styles/
├── abstracts/
│   ├── _variables.scss     // カラー、サイズなどの変数
│   ├── _mixins.scss       // 再利用可能なmixin
│   └── _functions.scss    // SCSS関数
├── base/
│   ├── _reset.scss        // CSSリセット
│   ├── _typography.scss   // タイポグラフィ
│   └── _global.scss       // グローバルスタイル
├── components/
│   ├── _button.scss       // ボタンのスタイル
│   ├── _card.scss         // カードのスタイル
│   └── _modal.scss        // モーダルのスタイル
├── layout/
│   ├── _header.scss       // ヘッダーのスタイル
│   ├── _footer.scss       // フッターのスタイル
│   └── _sidebar.scss      // サイドバーのスタイル
└── main.scss              // メインファイル

この構成により、スタイルの管理とメンテナンスが効率化されます。

メインSCSSファイル

scss// assets/styles/main.scss

// 1. 抽象化層
@import 'abstracts/variables';
@import 'abstracts/mixins';
@import 'abstracts/functions';

// 2. ベース層
@import 'base/reset';
@import 'base/typography';
@import 'base/global';

// 3. レイアウト層
@import 'layout/header';
@import 'layout/footer';
@import 'layout/sidebar';

// 4. コンポーネント層
@import 'components/button';
@import 'components/card';
@import 'components/modal';

インポートの順序を統一することで、スタイルの優先順位を管理しやすくなります。

変数ファイルの例

scss// assets/styles/abstracts/_variables.scss

// カラーパレット
$primary-color: #3498db;
$secondary-color: #2ecc71;
$danger-color: #e74c3c;
$warning-color: #f39c12;

// グレースケール
$gray-50: #f8f9fa;
$gray-100: #e9ecef;
$gray-200: #dee2e6;
$gray-900: #212529;

// サイズ
$spacing-xs: 0.25rem;
$spacing-sm: 0.5rem;
$spacing-md: 1rem;
$spacing-lg: 1.5rem;
$spacing-xl: 3rem;

// ブレークポイント
$breakpoint-sm: 576px;
$breakpoint-md: 768px;
$breakpoint-lg: 992px;
$breakpoint-xl: 1200px;

よく使用される値を変数として定義することで、一貫性のあるデザインを実現できます。

図で理解できる要点:

  • ディレクトリ構造は機能と技術で明確に分離されている
  • 各層の依存関係は一方向に保たれている
  • アセット管理は用途別に体系化されている

まとめ

Vue.jsプロジェクトにおける効果的なディレクトリ設計は、開発効率と長期的な保守性を大きく左右します。本記事でご紹介した基本構成パターンは、小〜中規模のプロジェクトで実証済みのアプローチです。

重要なポイントの振り返り

適切なディレクトリ設計の核心は、役割の明確化と依存関係の整理にあります。各ディレクトリが持つ責任を明確に定義し、一方向の依存関係を保つことで、理解しやすく変更に強いコードベースを構築できるでしょう。

コンポーネントの分類においては、再利用性を基準として uifeatureslayout に分けることが効果的です。この分類により、適切なコンポーネントを素早く見つけ、重複を避けながら開発を進められます。

実践への第一歩

新しいプロジェクトを始める際は、今回ご紹介した基本構造をベースに、プロジェクトの特性に合わせて調整してください。既存プロジェクトのリファクタリングを検討されている場合は、段階的にディレクトリを整理していくアプローチをおすすめします。

チーム開発では、ディレクトリ設計のガイドラインを文書化し、全メンバーが共通認識を持てるようにすることが重要です。明確なルールがあることで、新しいメンバーのオンボーディングもスムーズに進むでしょう。

効果的なディレクトリ設計は、一度決めれば終わりではありません。プロジェクトの成長とともに継続的に見直し、改善していくことで、長期的な成功を実現できますね。

関連リンク