※これは「株式会社ネクスウェイ Advent Calendar 2025」20日目の記事です🎄
あらゆるコミュニケーションを支援する株式会社ネクスウェイの開発メンバーが、好きな技術・取り組み・学びをゆるく書いていきます ![]()
Nuxtのアプリケーションで採用してみて、中々よかったディレクトリ設計を紹介します。
どうやって設計するか
Nuxt(v4)の主要なディレクトリ構造は以下のようになっています。
- assets/: website's assets that the build tool (Vite or webpack) will process
- components/: Vue components of the application
- composables/: add your Vue composables
- layouts/: Vue components that wrap around your pages and avoid re-rendering between pages
- middleware/: run code before navigating to a particular route
- pages/: file-based routing to create routes within your web application
- plugins/: use Vue plugins and more at the creation of your Nuxt application
- utils/: add functions throughout your application that can be used in your components, composables, and pages.
これらのディレクトリのうち、特に複雑化が予測されるのは components/ composables/ pages/ です。
なので、これらのディレクトリの内部をどう設計するかが鍵になります。
とはいっても、指針は一つで、「関連の強いファイルは近くに置く」というだけです。
関連の強いファイルは近くに置く
関連の強いファイルとは、言い換えれば 「一緒に変更される可能性が高いファイル」 です。SRPと言っていいのかわかりませんが、その応用みたいな感じですね。
経験上、ディレクトリを関連の強さを考えず、技術的な観点(Component / Composable / Pageのような区分)のみによって分割すると、あとでスケールしたときに地獄を見ることになります。
- 関連ファイルが各々のディレクトリに分散しているために、一つの改修のためにそれらすべてを渡り歩く必要がある
- 区分に該当しないファイルの置き場所に困る
等々です。
「関連が強い」というと技術的な観点も「関連」ではあるので表現が難しいですね。
「責務」もあまりしっくりこないので、ここでは単に「関連が強い」と表現しておきます。
実際に設計してみる
この設計は、いわゆる コロケーション(colocation) の考え方に近いものです。
コンポーネント、ロジック、型定義などを技術別に分けるのではなく、「一緒に変更される単位」で近くに置くことで、変更の影響範囲を局所化します。
components/
components/ 配下は ui/ と pages/ に分けます。
components/
ui/
pages/
-
ui/は汎用的なコンポーネント -
pages/はページごとのコンポーネント
を保持するディレクトリです。
ここでは、コンポーネントを「汎化されたもの」と「ページに特化したもの」に分けています。
再利用前提かどうかを最初に分けておくことで、設計上の迷いを減らしています。
components/pages/ 配下にはページごとのディレクトリを作成し、以下のような関連が強いロジックを全て配置します。
- ページに特化したコンポーザブル
- ページに特化したコンポーネント
- ページで扱う定数、関数、TypeScriptの型
composables/
ページごとのロジックは components/pages/ に配置するため、このディレクトリにはページ間で共通のロジック(API関連など)を置きます。
pages/
こちらもページごとのロジックは components/pages/ に配置するため、このディレクトリのファイルはルーティングとメタ情報の定義に特化させます。
pages/ と components/pages/ の区別について
あるページの
- ルーティング
- メタ情報の定義
と
- マークアップ
- ロジック
について、これらは一緒に変更される可能性は低いと思われるのでディレクトリを分けています。
pages/ のコンポーネントはこんな感じです。
<script setup lang="ts">
definePageMeta({
title: 'ログイン画面',
auth: false,
})
</script>
<template>
<!-- PageXxx で components/pages/ が参照されるように自動インポートの設定をしています -->
<PageLogin />
</template>
他方、components/pages/ にはページのコンテンツを置きます。
<script setup lang="ts">
import useLogin from './useLogin'
const email = ref('')
const password = ref('')
const { login } = useLogin(email, password)
</script>
<template>
<LoginForm
:email="email"
:password="password"
@sumit="login"
/>
</template>
設計の例
components/
ui/ # 汎用コンポーネント + コンポーザブル
modal/
BaseModal.vue
useModal.ts
BaseButton.vue
pages/ # ページごとのコンポーネント + コンポーザブル
login/
LoginForm.vue
useLogin.ts
index.ts
users/
list/
useUserList.ts
index.ts
detail/
useUserDetail.ts
index.ts
composables/ # 汎用コンポーザブル
useApi.ts
useDrag.ts
useJwt.ts
pages/ # ページのルーティング
users/
[user_id].vue
index.vue
login.vue
まとめ
技術的な分類は分かりやすい反面、変更のたびにディレクトリを横断する構造を生みがちです。
一方で、変更単位でファイルを近づけておくと、改修は局所化し、考える範囲も自然と狭くなります。
もしスケールしてきたNuxtプロジェクトで、「なんとなく触りづらい」「変更が面倒」
と感じ始めたら、一度ディレクトリ構造を変更の単位から見直してみると、意外とあっさり整理できるかもしれません。
最後まで読んでいただきありがとうございました。
株式会社ネクスウェイ Advent Calendar 2025では明日以降も、開発メンバーがそれぞれの「好き」と「学び」を自分の言葉で綴っていく予定です。
明日 21 日目の記事は、同じくネクスウェイ開発メンバーの @saocy_penguinさん です。
引き続き、お楽しみください🎄
もっと他の記事も読んでみたい方
当社に興味がある方はこちら👀