T-CREATOR

Nuxt のディレクトリ構成と役割を図解で解説

Nuxt のディレクトリ構成と役割を図解で解説

Nuxt プロジェクトを始めた方が最初に迷うのが、どのディレクトリに何を置けばいいかわからないという問題です。今回は、Nuxt の各ディレクトリの役割と使い方を図解でわかりやすく解説していきます。

開発を始めたばかりの頃、私も「このファイルはどこに置けばいいんだろう?」と何度も迷いました。そんな経験をお持ちの方に、少しでも役立つ情報をお届けできれば幸いです。

Nuxt プロジェクトの全体像

ディレクトリ構成の基本原則

Nuxt は「Convention over Configuration」(設定よりも規約)の哲学に基づいて設計されています。これは、開発者が迷いやすい設定を規約として定めることで、より素早く開発に集中できるようにする考え方です。

Nuxt プロジェクトの基本的なディレクトリ構成は以下のようになります:

csharpmy-nuxt-app/
├── assets/           # Webpackで処理される静的ファイル
├── components/       # Vueコンポーネント
├── layouts/          # アプリケーションレイアウト
├── middleware/       # カスタムミドルウェア
├── pages/            # ページコンポーネント(ルーティング)
├── plugins/          # JavaScriptプラグイン
├── public/           # 静的ファイル(直接配信)
├── server/           # サーバーサイドAPI
├── store/            # Vuex状態管理
├── nuxt.config.js    # Nuxt設定ファイル
└── package.json      # 依存関係とスクリプト

この構成を見ると、各ディレクトリが明確な役割を持っていることがわかります。適切な場所にファイルを配置することで、Nuxt が自動的に必要な処理を行ってくれるのです。

自動読み込み機能の仕組み

Nuxt の最も素晴らしい特徴の一つが、自動読み込み機能です。これは、特定のディレクトリにファイルを配置するだけで、Nuxt が自動的にそのファイルを認識し、適切な処理を行ってくれる機能です。

例えば、以下のような自動読み込みが行われます:

ディレクトリ自動読み込み内容
pages​/​ルーティング設定
components​/​コンポーネント登録
layouts​/​レイアウト登録
middleware​/​ミドルウェア登録
plugins​/​プラグイン登録
server​/​api​/​API エンドポイント

この自動読み込み機能により、開発者は複雑な設定ファイルを書く必要がなく、ファイルを適切な場所に配置するだけで機能が動作します。

必須ディレクトリの詳細解説

pages ディレクトリ:ルーティングの心臓部

pagesディレクトリは、Nuxt アプリケーションの心臓部といえる重要なディレクトリです。ここに Vue コンポーネントを配置することで、自動的にルーティングが生成されます。

基本的なページの作成

まず、シンプルなページを作成してみましょう。

vue<!-- pages/index.vue -->
<template>
  <div>
    <h1>ホームページ</h1>
    <p>Nuxtへようこそ!</p>
  </div>
</template>

<script setup>
// ページコンポーネントの基本構造
// <script setup>を使うことで、より簡潔に書けます
</script>

<style scoped>
/* コンポーネント固有のスタイル */
h1 {
  color: #2c3e50;
  text-align: center;
}
</style>

このファイルをpages​/​index.vueに配置すると、自動的に​/​ルートに対応するページが作成されます。

動的ルーティングの実装

商品詳細ページのような動的なルーティングも簡単に実装できます。

vue<!-- pages/products/[id].vue -->
<template>
  <div>
    <h1>商品詳細</h1>
    <p>商品ID: {{ $route.params.id }}</p>
    <div v-if="product">
      <h2>{{ product.name }}</h2>
      <p>価格: ¥{{ product.price }}</p>
    </div>
  </div>
</template>

<script setup>
// 動的ルーティングのパラメータを取得
const route = useRoute();
const productId = route.params.id;

// 商品データの取得(実際のAPIコール例)
const { data: product } = await $fetch(
  `/api/products/${productId}`
);
</script>

ファイル名を[id].vueとすることで、​/​products​/​123のような URL にアクセスした時に、123idパラメータとして渡されます。

よくあるエラーと対処法

pages ディレクトリで初心者がよく遭遇するエラーをご紹介します。

javascriptError: Cannot read properties of undefined (reading 'params')

これは、$routeオブジェクトが正しく取得できていない時に発生するエラーです。解決策は以下の通りです:

vue<!-- 正しい書き方 -->
<script setup>
const route = useRoute();
const id = route.params.id;
</script>

<!-- 間違った書き方 -->
<script setup>
const id = $route.params.id; // $routeは使えません
</script>

components ディレクトリ:再利用可能なパーツの管理

componentsディレクトリは、再利用可能な Vue コンポーネントを管理する場所です。Nuxt 3 では、このディレクトリに配置したコンポーネントが自動的にインポートされます。

基本的なコンポーネントの作成

ボタンコンポーネントを例に、基本的な作成方法を見てみましょう。

vue<!-- components/BaseButton.vue -->
<template>
  <button
    :class="buttonClass"
    @click="$emit('click')"
    :disabled="disabled"
  >
    <slot></slot>
  </button>
</template>

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

// イベントの定義
const emit = defineEmits(['click']);
</script>

このコンポーネントは、以下のように使用できます:

vue<!-- pages/index.vue -->
<template>
  <div>
    <!-- 自動インポートされるため、import文不要 -->
    <BaseButton @click="handleClick" variant="primary">
      クリックしてください
    </BaseButton>
    <BaseButton @click="handleDelete" variant="danger">
      削除
    </BaseButton>
  </div>
</template>

<script setup>
const handleClick = () => {
  console.log('ボタンがクリックされました');
};

const handleDelete = () => {
  console.log('削除処理を実行します');
};
</script>

コンポーネントの階層構造

大規模なプロジェクトでは、コンポーネントを階層的に管理することが重要です。

csscomponents/
├── base/
│   ├── BaseButton.vue
│   ├── BaseInput.vue
│   └── BaseCard.vue
├── layout/
│   ├── Header.vue
│   ├── Footer.vue
│   └── Sidebar.vue
└── form/
    ├── LoginForm.vue
    └── ContactForm.vue

この構造により、コンポーネントは以下のように使用できます:

vue<!-- 階層構造に基づいた自動的な命名 -->
<template>
  <div>
    <BaseButton />
    <!-- components/base/BaseButton.vue -->
    <LayoutHeader />
    <!-- components/layout/Header.vue -->
    <FormLoginForm />
    <!-- components/form/LoginForm.vue -->
  </div>
</template>

layouts ディレクトリ:共通レイアウトの定義

layoutsディレクトリは、アプリケーション全体の共通レイアウトを定義する場所です。ヘッダーやフッターなど、複数のページで共通して使用される要素を管理できます。

デフォルトレイアウトの作成

最も基本的なレイアウトファイルを作成してみましょう。

vue<!-- layouts/default.vue -->
<template>
  <div class="app-layout">
    <!-- ヘッダー部分 -->
    <header class="app-header">
      <nav class="navbar">
        <NuxtLink to="/" class="logo"> My App </NuxtLink>
        <ul class="nav-links">
          <li><NuxtLink to="/">ホーム</NuxtLink></li>
          <li><NuxtLink to="/about">概要</NuxtLink></li>
          <li>
            <NuxtLink to="/contact">お問い合わせ</NuxtLink>
          </li>
        </ul>
      </nav>
    </header>

    <!-- メインコンテンツ -->
    <main class="app-main">
      <slot />
    </main>

    <!-- フッター部分 -->
    <footer class="app-footer">
      <p>&copy; 2024 My App. All rights reserved.</p>
    </footer>
  </div>
</template>

<style scoped>
.app-layout {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.app-header {
  background-color: #2c3e50;
  color: white;
  padding: 1rem 0;
}

.navbar {
  max-width: 1200px;
  margin: 0 auto;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 1rem;
}
</style>

このレイアウトは、<slot ​/​>の部分にページコンテンツが挿入される仕組みになっています。

カスタムレイアウトの作成

管理画面用など、特別なレイアウトが必要な場合は、カスタムレイアウトを作成できます。

vue<!-- layouts/admin.vue -->
<template>
  <div class="admin-layout">
    <aside class="sidebar">
      <h2>管理メニュー</h2>
      <nav class="admin-nav">
        <NuxtLink to="/admin/dashboard"
          >ダッシュボード</NuxtLink
        >
        <NuxtLink to="/admin/users">ユーザー管理</NuxtLink>
        <NuxtLink to="/admin/products">商品管理</NuxtLink>
      </nav>
    </aside>

    <main class="admin-main">
      <slot />
    </main>
  </div>
</template>

<style scoped>
.admin-layout {
  display: flex;
  min-height: 100vh;
}

.sidebar {
  width: 250px;
  background-color: #34495e;
  color: white;
  padding: 1rem;
}

.admin-main {
  flex: 1;
  padding: 2rem;
}
</style>

ページでこのレイアウトを使用する場合は、以下のように指定します:

vue<!-- pages/admin/dashboard.vue -->
<template>
  <div>
    <h1>管理ダッシュボード</h1>
    <p>管理者専用ページです</p>
  </div>
</template>

<script setup>
// カスタムレイアウトの指定
definePageMeta({
  layout: 'admin',
});
</script>

重要ディレクトリの役割と使い分け

assets ディレクトリ:静的ファイル管理の基本

assetsディレクトリは、Webpack によって処理される静的ファイルを配置する場所です。CSS、画像、フォントなどのファイルがここに格納されます。

CSS ファイルの管理

グローバルなスタイルシートを管理する例を見てみましょう。

css/* assets/css/main.css */
/* グローバルスタイル */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: 'Helvetica Neue', Arial, sans-serif;
  line-height: 1.6;
  color: #333;
}

/* ユーティリティクラス */
.container {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 1rem;
}

.btn {
  display: inline-block;
  padding: 0.5rem 1rem;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  text-decoration: none;
  transition: all 0.3s ease;
}

.btn-primary {
  background-color: #3498db;
  color: white;
}

.btn-primary:hover {
  background-color: #2980b9;
}

この CSS ファイルをnuxt.config.jsで読み込みます:

javascript// nuxt.config.js
export default defineNuxtConfig({
  css: ['~/assets/css/main.css'],

  // SCSSを使用する場合
  // css: [
  //   '~/assets/scss/main.scss'
  // ]
});

画像ファイルの最適化

画像ファイルも assets ディレクトリで管理できます。Nuxt は自動的に画像を最適化してくれます。

vue<!-- コンポーネントでの画像使用例 -->
<template>
  <div class="hero-section">
    <!-- assets内の画像を参照 -->
    <img
      src="~/assets/images/hero-bg.jpg"
      alt="ヒーロー画像"
      class="hero-image"
    />

    <!-- NuxtImgコンポーネントを使用した最適化 -->
    <NuxtImg
      src="/assets/images/hero-bg.jpg"
      alt="ヒーロー画像"
      width="1200"
      height="600"
      loading="lazy"
    />
  </div>
</template>

public ディレクトリ:Webpack を通さない静的ファイル

publicディレクトリ(Nuxt 2 ではstatic)は、Webpack の処理を通さず、そのまま配信される静的ファイルを置く場所です。

使い分けの基準

用途配置場所理由
CSS ファイルassets/最小化・最適化が必要
画像(コンポーネント内)assets/最適化・リサイズが必要
favicon.icopublic/変更される可能性が低い
robots.txtpublic/そのまま配信する必要がある
外部 API 用画像public/直接 URL でアクセスしたい
arduinopublic/
├── favicon.ico
├── robots.txt
├── sitemap.xml
└── images/
    ├── og-image.jpg
    └── logo.png

public ディレクトリの画像は、以下のように直接パスで参照できます:

vue<template>
  <div>
    <!-- public/images/logo.png への参照 -->
    <img src="/images/logo.png" alt="ロゴ" />

    <!-- OGP画像の設定 -->
    <Head>
      <meta
        property="og:image"
        content="/images/og-image.jpg"
      />
    </Head>
  </div>
</template>

middleware ディレクトリ:ルート処理の前後処理

middlewareディレクトリは、ページがレンダリングされる前に実行される処理を定義する場所です。認証チェックやアクセス制御など、重要なロジックを実装できます。

認証ミドルウェアの実装

ログインが必要なページにアクセスする前に、認証状態をチェックするミドルウェアを作成してみましょう。

javascript// middleware/auth.js
export default defineNuxtRouteMiddleware((to, from) => {
  // ユーザーの認証状態を取得
  const user = useAuthUser();

  if (!user.value) {
    // 未認証の場合、ログインページにリダイレクト
    return navigateTo('/login');
  }
});

このミドルウェアを特定のページで使用する場合:

vue<!-- pages/dashboard.vue -->
<template>
  <div>
    <h1>ダッシュボード</h1>
    <p>認証済みユーザーのみアクセス可能</p>
  </div>
</template>

<script setup>
// ミドルウェアの適用
definePageMeta({
  middleware: 'auth',
});
</script>

管理者権限チェックのミドルウェア

より高度な権限チェックを行うミドルウェアも作成できます。

javascript// middleware/admin.js
export default defineNuxtRouteMiddleware((to, from) => {
  const user = useAuthUser();

  // 認証チェック
  if (!user.value) {
    throw createError({
      statusCode: 401,
      statusMessage: 'ログインが必要です',
    });
  }

  // 管理者権限チェック
  if (user.value.role !== 'admin') {
    throw createError({
      statusCode: 403,
      statusMessage: 'アクセス権限がありません',
    });
  }
});

このミドルウェアを使用した場合、権限がないユーザーには適切なエラーが表示されます。

plugins ディレクトリ:グローバル機能の追加

pluginsディレクトリは、アプリケーション全体で使用するプラグインを配置する場所です。サードパーティライブラリの設定や、カスタムディレクティブの追加などに使用します。

API クライアントの設定

Axios を使用した API クライアントの設定例を見てみましょう。

javascript// plugins/api.client.js
import axios from 'axios';

export default defineNuxtPlugin(() => {
  // デフォルト設定
  const api = axios.create({
    baseURL: 'https://api.example.com',
    timeout: 10000,
    headers: {
      'Content-Type': 'application/json',
    },
  });

  // リクエストインターセプター
  api.interceptors.request.use(
    (config) => {
      // 認証トークンの追加
      const token = useCookie('auth-token');
      if (token.value) {
        config.headers.Authorization = `Bearer ${token.value}`;
      }
      return config;
    },
    (error) => {
      return Promise.reject(error);
    }
  );

  // レスポンスインターセプター
  api.interceptors.response.use(
    (response) => response,
    (error) => {
      if (error.response?.status === 401) {
        // 認証エラーの場合、ログインページへリダイレクト
        navigateTo('/login');
      }
      return Promise.reject(error);
    }
  );

  // グローバルに$apiとして提供
  return {
    provide: {
      api,
    },
  };
});

このプラグインにより、アプリケーション全体で$apiを使用できます:

vue<!-- 使用例 -->
<script setup>
const { $api } = useNuxtApp();

// APIコールの実行
const fetchUserData = async () => {
  try {
    const response = await $api.get('/users/me');
    return response.data;
  } catch (error) {
    console.error(
      'ユーザー情報の取得に失敗しました:',
      error
    );
    throw error;
  }
};
</script>

高度なディレクトリ活用術

store ディレクトリ:Vuex 状態管理の実装

storeディレクトリは、Vuex(または Pinia)を使用した状態管理を実装する場所です。アプリケーションの状態を一元管理できます。

Pinia を使用した状態管理

現在推奨されている Pinia を使用した状態管理の例をご紹介します。

javascript// store/user.js
import { defineStore } from 'pinia';

export const useUserStore = defineStore('user', {
  state: () => ({
    user: null,
    loading: false,
    error: null,
  }),

  getters: {
    // ユーザーがログインしているかどうか
    isLoggedIn: (state) => !!state.user,

    // ユーザーの表示名
    displayName: (state) => {
      if (!state.user) return 'ゲスト';
      return state.user.displayName || state.user.email;
    },

    // ユーザーが管理者かどうか
    isAdmin: (state) => {
      return state.user?.role === 'admin';
    },
  },

  actions: {
    // ユーザー情報の取得
    async fetchUser() {
      this.loading = true;
      this.error = null;

      try {
        const { $api } = useNuxtApp();
        const response = await $api.get('/users/me');
        this.user = response.data;
      } catch (error) {
        this.error = error.message;
        this.user = null;
      } finally {
        this.loading = false;
      }
    },

    // ログイン処理
    async login(credentials) {
      this.loading = true;
      this.error = null;

      try {
        const { $api } = useNuxtApp();
        const response = await $api.post(
          '/auth/login',
          credentials
        );

        // トークンの保存
        const token = useCookie('auth-token');
        token.value = response.data.token;

        // ユーザー情報の設定
        this.user = response.data.user;

        return true;
      } catch (error) {
        this.error =
          error.response?.data?.message ||
          'ログインに失敗しました';
        return false;
      } finally {
        this.loading = false;
      }
    },

    // ログアウト処理
    async logout() {
      try {
        const { $api } = useNuxtApp();
        await $api.post('/auth/logout');
      } catch (error) {
        console.error(
          'ログアウト処理でエラーが発生しました:',
          error
        );
      } finally {
        // クライアントサイドの状態をクリア
        this.user = null;
        const token = useCookie('auth-token');
        token.value = null;
      }
    },
  },
});

コンポーネントでの使用例:

vue<!-- pages/profile.vue -->
<template>
  <div>
    <div v-if="userStore.loading" class="loading">
      読み込み中...
    </div>

    <div v-else-if="userStore.error" class="error">
      エラー: {{ userStore.error }}
    </div>

    <div v-else-if="userStore.isLoggedIn" class="profile">
      <h1>プロフィール</h1>
      <p>ようこそ、{{ userStore.displayName }}さん</p>

      <div v-if="userStore.isAdmin" class="admin-panel">
        <h2>管理者メニュー</h2>
        <NuxtLink to="/admin">管理画面へ</NuxtLink>
      </div>

      <button @click="handleLogout" class="logout-btn">
        ログアウト
      </button>
    </div>
  </div>
</template>

<script setup>
const userStore = useUserStore();

// ページ表示時にユーザー情報を取得
onMounted(() => {
  if (userStore.isLoggedIn) {
    userStore.fetchUser();
  }
});

// ログアウト処理
const handleLogout = async () => {
  await userStore.logout();
  await navigateTo('/login');
};
</script>

server ディレクトリ:API ルートの作成

serverディレクトリは、Nuxt アプリケーション内でサーバーサイド API を作成する場所です。フルスタックアプリケーションを構築する際に非常に有用です。

基本的な API エンドポイント

ユーザー管理 API の例を見てみましょう。

javascript// server/api/users/index.get.js
export default defineEventHandler(async (event) => {
  // クエリパラメータの取得
  const query = getQuery(event);
  const page = parseInt(query.page) || 1;
  const limit = parseInt(query.limit) || 10;

  try {
    // データベースからユーザー一覧を取得
    // 実際のプロジェクトではPrismaやTypeORMなどのORMを使用
    const users = await getUsersFromDatabase({
      page,
      limit,
      search: query.search,
    });

    return {
      success: true,
      data: users,
      pagination: {
        page,
        limit,
        total: users.length,
      },
    };
  } catch (error) {
    throw createError({
      statusCode: 500,
      statusMessage: 'ユーザー一覧の取得に失敗しました',
    });
  }
});

POST リクエストの処理

ユーザー作成 API の例:

javascript// server/api/users/index.post.js
export default defineEventHandler(async (event) => {
  // リクエストボディの取得
  const body = await readBody(event);

  // バリデーション
  const validationErrors = validateUserData(body);
  if (validationErrors.length > 0) {
    throw createError({
      statusCode: 400,
      statusMessage: 'バリデーションエラー',
      data: validationErrors,
    });
  }

  try {
    // パスワードのハッシュ化
    const hashedPassword = await hashPassword(
      body.password
    );

    // データベースに保存
    const newUser = await createUser({
      email: body.email,
      password: hashedPassword,
      displayName: body.displayName,
    });

    // パスワードを除外してレスポンス
    const { password, ...userWithoutPassword } = newUser;

    return {
      success: true,
      data: userWithoutPassword,
      message: 'ユーザーが正常に作成されました',
    };
  } catch (error) {
    if (error.code === 'UNIQUE_CONSTRAINT_ERROR') {
      throw createError({
        statusCode: 409,
        statusMessage:
          'このメールアドレスは既に使用されています',
      });
    }

    throw createError({
      statusCode: 500,
      statusMessage: 'ユーザーの作成に失敗しました',
    });
  }
});

// バリデーション関数
function validateUserData(data) {
  const errors = [];

  if (!data.email || !isValidEmail(data.email)) {
    errors.push({
      field: 'email',
      message: '有効なメールアドレスを入力してください',
    });
  }

  if (!data.password || data.password.length < 8) {
    errors.push({
      field: 'password',
      message: 'パスワードは8文字以上で入力してください',
    });
  }

  return errors;
}

動的ルートの処理

特定のユーザーの詳細を取得する API の例:

javascript// server/api/users/[id].get.js
export default defineEventHandler(async (event) => {
  // URLパラメータの取得
  const userId = getRouterParam(event, 'id');

  // IDの検証
  if (!userId || isNaN(parseInt(userId))) {
    throw createError({
      statusCode: 400,
      statusMessage: '無効なユーザーIDです',
    });
  }

  try {
    const user = await getUserById(parseInt(userId));

    if (!user) {
      throw createError({
        statusCode: 404,
        statusMessage: 'ユーザーが見つかりません',
      });
    }

    // パスワードを除外してレスポンス
    const { password, ...userWithoutPassword } = user;

    return {
      success: true,
      data: userWithoutPassword,
    };
  } catch (error) {
    if (error.statusCode) {
      throw error;
    }

    throw createError({
      statusCode: 500,
      statusMessage: 'ユーザー情報の取得に失敗しました',
    });
  }
});

modules ディレクトリ:カスタムモジュールの管理

modulesディレクトリは、プロジェクト固有のカスタムモジュールを管理する場所です。再利用可能な機能をモジュール化して整理できます。

認証モジュールの作成

認証機能をモジュール化した例をご紹介します。

javascript// modules/auth/index.js
import {
  defineNuxtModule,
  addPlugin,
  createResolver,
} from '@nuxt/kit';

export default defineNuxtModule({
  meta: {
    name: 'auth-module',
    configKey: 'auth',
  },

  defaults: {
    strategies: {
      local: {
        endpoints: {
          login: { url: '/auth/login', method: 'post' },
          logout: { url: '/auth/logout', method: 'post' },
          user: { url: '/auth/user', method: 'get' },
        },
      },
    },
    redirect: {
      login: '/login',
      logout: '/login',
      home: '/',
    },
  },

  setup(options, nuxt) {
    const resolver = createResolver(import.meta.url);

    // プラグインの追加
    addPlugin(resolver.resolve('./plugin.js'));

    // ミドルウェアの追加
    addPlugin(resolver.resolve('./middleware.js'));

    // 設定をランタイムに渡す
    nuxt.options.runtimeConfig.public.auth = options;
  },
});

実践的なディレクトリ設計パターン

小規模プロジェクトでの最適構成

個人ブログや小規模な Web サイトに適した構成をご紹介します。

cssmy-blog/
├── components/
│   ├── BlogCard.vue
│   ├── BlogList.vue
│   ├── Header.vue
│   └── Footer.vue
├── pages/
│   ├── index.vue
│   ├── about.vue
│   ├── contact.vue
│   └── blog/
│       ├── index.vue
│       └── [slug].vue
├── layouts/
│   └── default.vue
├── assets/
│   └── css/
│       └── main.css
├── public/
│   ├── images/
│   └── favicon.ico
├── content/
│   └── blog/
│       ├── first-post.md
│       └── second-post.md
└── nuxt.config.js

この構成では、シンプルでありながら必要な機能を網羅しています。

大規模プロジェクトでの構成例

エンタープライズレベルのアプリケーションに適した構成例:

bashenterprise-app/
├── components/
│   ├── base/
│   │   ├── BaseButton.vue
│   │   ├── BaseInput.vue
│   │   └── BaseModal.vue
│   ├── layout/
│   │   ├── AppHeader.vue
│   │   ├── AppSidebar.vue
│   │   └── AppFooter.vue
│   ├── forms/
│   │   ├── UserForm.vue
│   │   └── ProductForm.vue
│   └── charts/
│       ├── LineChart.vue
│       └── BarChart.vue
├── pages/
│   ├── index.vue
│   ├── auth/
│   │   ├── login.vue
│   │   └── register.vue
│   ├── dashboard/
│   │   ├── index.vue
│   │   └── analytics.vue
│   └── admin/
│       ├── users/
│       │   ├── index.vue
│       │   └── [id].vue
│       └── products/
│           ├── index.vue
│           └── [id].vue
├── layouts/
│   ├── default.vue
│   ├── auth.vue
│   └── admin.vue
├── middleware/
│   ├── auth.js
│   ├── admin.js
│   └── guest.js
├── plugins/
│   ├── api.client.js
│   ├── charts.client.js
│   └── validation.js
├── store/
│   ├── user.js
│   ├── products.js
│   └── dashboard.js
├── server/
│   ├── api/
│   │   ├── auth/
│   │   ├── users/
│   │   └── products/
│   └── middleware/
│       └── cors.js
├── composables/
│   ├── useAuth.js
│   ├── useApi.js
│   └── useValidation.js
└── utils/
    ├── formatters.js
    ├── validators.js
    └── constants.js

チーム開発での運用ルール

チームでの開発を効率化するためのディレクトリ運用ルールをご紹介します。

命名規則

種類規則
コンポーネントPascalCaseUserProfile.vue
ページkebab-caseuser-profile.vue
プラグインcamelCaseapiClient.js
ミドルウェアcamelCaseauthCheck.js
ストアcamelCaseuserStore.js

コードレビューチェックリスト

markdown# ディレクトリ構成チェックリスト

## 配置場所

- [ ] ファイルが適切なディレクトリに配置されているか
- [ ] 命名規則に従っているか
- [ ] 既存のファイルとの重複がないか

## コンポーネント

- [ ] 再利用可能なコンポーネントは適切に分割されているか
- [ ] プロップスとイベントが適切に定義されているか
- [ ] スタイルが scoped になっているか

## ページ

- [ ] ページメタ情報が適切に設定されているか
- [ ] 必要なミドルウェアが適用されているか
- [ ] SEO 対策が実装されているか

## API

- [ ] エラーハンドリングが適切に実装されているか
- [ ] バリデーションが実装されているか
- [ ] セキュリティ対策が実装されているか

まとめ

Nuxt のディレクトリ構成は、最初は複雑に感じられるかもしれませんが、それぞれに明確な役割と目的があります。適切なディレクトリにファイルを配置することで、Nuxt の自動読み込み機能や最適化機能を最大限に活用できます。

重要なポイントをまとめると:

  • pages: ルーティングの自動生成
  • components: コンポーネントの自動インポート
  • layouts: 共通レイアウトの管理
  • assets: 最適化される静的ファイル
  • public: 直接配信される静的ファイル
  • middleware: ページ表示前の処理
  • plugins: グローバル機能の追加
  • store: 状態管理の実装
  • server: API ルートの作成

これらのディレクトリを適切に使い分けることで、保守性が高く、スケーラブルな Nuxt アプリケーションを構築できます。

開発を始めた頃は「どこに何を置けばいいかわからない」という状況が続くかもしれませんが、実際に手を動かしながら学んでいくことで、自然と Nuxt の哲学を理解できるようになります。

ぜひ、この記事を参考にして、あなたの Nuxt プロジェクトをより良いものにしてください。

関連リンク