Vue 3 + TypeScriptベストプラクティス:2025年版エンタープライズアーキテクチャ

はじめに
先月、チームで技術選定会議をした時のことです。プロダクトマネージャーが退室した後、フロントエンドエンジニア同士で激論になりました。「Vuexを使い続けるか」「Piniaに移行するか」「フォルダ構成はどうするか」「型定義はどこまで厳密にするか」。
正直に言うと、私が初めてVue 3 + TypeScript環境を構築した時は、tsconfig.json の設定だけで半日潰しました。「完璧だ」と思っても、別のPCでcloneすると動かなかったり。あの絶望感、経験したことがある方も多いのではないでしょうか。
この記事は、私たちのチームが数々の失敗を経て辿り着いた「2025年時点での最適解」です。小規模な個人開発から中規模以上のエンタープライズ開発まで耐えうる、堅牢なアーキテクチャを紹介します。
2025年のVue 3技術スタック選定
結論から言うと、現在のデファクトスタンダードは以下の通りです:
Vite + Vue 3 + TypeScript + Pinia + Vue Router 4
Viteの高速なHMR(ホットモジュールリプレースメント)はもはや必須です。Vue 3.6のVapor Mode(仮想DOMなしで直接DOM操作するモード)も期待されていますが、まずは安定したこのセットアップを推奨します。
なぜPiniaなのか?
Vuexユーザーの多くがPiniaへの移行をためらいますが、APIの簡潔さを見れば戻れなくなります。
// ❌ Vuex (冗長)
const store = createStore({
state: () => ({ count: 0 }),
mutations: {
increment(state) { state.count++ }
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => commit('increment'), 1000)
}
}
})
// ✅ Pinia (シンプル)
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const increment = () => count.value++
// 以前のactions相当。普通の関数でOK
const incrementAsync = () => setTimeout(increment, 1000)
return { count, increment, incrementAsync }
})プロジェクト・ディレクトリ構造
「src直下にファイルを全部置く」のは卒業しましょう。かといって細かすぎても迷子になります。
私たちが採用しているのは「機能ドメイン」による分割です。
src/
├── api/ # API通信層
│ ├── modules/ # 業務モジュールごと
│ │ ├── user.ts
│ │ └── order.ts
│ └── index.ts
├── assets/ # 静的リソース
├── components/ # 汎用コンポーネント
│ ├── base/ # ボタン、入力フォームなど原子コンポーネント
│ └── business/ # 業務共通コンポーネント
├── composables/ # 状態を持つロジック(Hooks)
│ ├── useAuth.ts
│ └── useRequest.ts
├── utils/ # 純粋なユーティリティ関数
├── layouts/ # レイアウト
├── router/ # ルーティング設定
├── stores/ # Piniaストア
├── types/ # グローバル型定義
├── views/ # ページコンポーネント
│ ├── user/
│ └── order/
├── App.vue
└── main.ts重要なポイント: composables と utils を明確に分けること。
composables:refやcomputedを使う「状態を持つロジック」。utils: 純粋な関数(日付フォーマット、バリデーションなど)。
TypeScript型定義のベストプラクティス
tsconfig.json の設定はこれで決まりです。
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"jsx": "preserve",
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["ESNext", "DOM"],
"skipLibCheck": true,
"noEmit": true,
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}strict: true は絶対にONにしてください。最初はエラーが出て面倒ですが、バグを未然に防ぐコストの方が安いです。
Vueコンポーネントでの型定義
<script setup lang="ts"> 内でのProps定義が非常に快適になりました。
<script setup lang="ts">
// Props型定義
interface Props {
title: string
count?: number
items: string[]
}
// マクロを使って定義(withDefaultsで初期値設定)
const props = withDefaults(defineProps<Props>(), {
count: 0,
items: () => []
})
// Emits定義
const emit = defineEmits<{
update: [value: string]
delete: [id: number]
}>()
</script>Pinia実践ガイド
Piniaを使う際は、Composition APIスタイル(setup関数スタイル)を強く推奨します。Options APIスタイルよりも柔軟で、Vueコンポーネントと同じメンタルモデルで書けるからです。
// stores/modules/user.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useUserStore = defineStore('user', () => {
// state
const token = ref<string>('')
// getters
const isLoggedIn = computed(() => !!token.value)
// actions
const setToken = (newToken: string) => {
token.value = newToken
localStorage.setItem('token', newToken)
}
return { token, isLoggedIn, setToken }
}, {
persist: true // pinia-plugin-persistedstateによる永続化
})注意点: ストアから値を解凍(destructure)する時は storeToRefs を使いましょう。そうしないとリアクティビティが失われます。
// ❌ ダメな例
const { isLoggedIn } = useUserStore() // 反応しなくなる
// ✅ 正しい例
const { isLoggedIn } = storeToRefs(useUserStore())Vue Router 4:型安全なルーティング
meta フィールドに型をつけることで、IDEの補完を効かせることができます。
// router/index.ts
import 'vue-router'
declare module 'vue-router' {
interface RouteMeta {
title?: string
requiresAuth?: boolean
roles?: string[]
}
}
// ...ルーティング設定コード品質と自動化(ESLint 9 & Prettier)
ESLint 9から設定ファイル形式が flat config に大きく変わりました。unplugin-auto-import を導入すると、ref や computed をいちいちimportしなくて済み、開発体験が向上します。
// vite.config.ts
import AutoImport from 'unplugin-auto-import/vite'
export default defineConfig({
plugins: [
AutoImport({
imports: ['vue', 'vue-router', 'pinia'],
dts: 'src/auto-imports.d.ts', // 型定義ファイルを自動生成
})
]
})まとめ
技術選定に「絶対の正解」はありませんが、「定石」はあります。
今回紹介した構成は、保守性、開発速度、品質のバランスが取れた、現時点でのベストプラクティスです。
ぜひあなたの次のプロジェクトで試してみてください。
2025年版Vue 3プロジェクト構築完全フロー
Vite + TS + Pinia + ESLint 9による企業級プロジェクトのセットアップ手順
⏱️ Estimated time: 1 hr
- 1
Step1: プロジェクト初期化
npm create vite@latest my-project -- --template vue-ts
cd my-project
npm install - 2
Step2: コアライブラリのインストール
npm install pinia vue-router@4
npm install -D sass - 3
Step3: ディレクトリ構造の整備
src/api, src/stores, src/views, src/composables, src/utils などのフォルダを作成。
機能ごとの責務を明確にする。 - 4
Step4: TypeScript設定 (tsconfig.json)
"strict": true, "moduleResolution": "bundler", "paths": { "@/*": ["./src/*"] } を設定。 - 5
Step5: Piniaセットアップ
main.tsでcreatePinia()を使用。
永続化が必要なら pinia-plugin-persistedstate を導入。 - 6
Step6: 自動化ツールの導入
unplugin-auto-import でAPIの自動インポートを設定。
ESLint 9 + Prettier でコード整形ルールを統一。
FAQ
Vuexを使ってはいけないのですか?
UtilityとComposableの違いは?
ESLint 9の設定が難しいです
2 min read · 公開日: 2025年11月24日 · 更新日: 2026年1月22日
関連記事
Next.js ファイルアップロード完全ガイド:S3/Qiniu Cloud 署名付き URL 直接アップロード実践

Next.js ファイルアップロード完全ガイド:S3/Qiniu Cloud 署名付き URL 直接アップロード実践
Next.js Eコマース実践:カートと Stripe 決済の完全実装ガイド

Next.js Eコマース実践:カートと Stripe 決済の完全実装ガイド
Next.js ユニットテスト実践:Jest + React Testing Library 完全設定ガイド


コメント
GitHubアカウントでログインしてコメントできます