TEXTBOOK SECTION / AI LEARNING

ディレクトリ構成

Square API × Next.js 実践EC開発|自社ECを低コストで作る、決済・在庫・注文管理の設計と実装の「完成コードと設計資料」より、ディレクトリ構成を解説。生成AI、AI活用、DX、業務改善を実践しながら学べるオンライン教材です。

2完成コードと設計資料EC

OVERVIEW

この節で学べること

概要を表示する
項目内容
教材名Square API × Next.js 実践EC開発|自社ECを低コストで作る、決済・在庫・注文管理の設計と実装
完成コードと設計資料
ディレクトリ構成
カテゴリEC
学習内容生成AI、AI活用、DX、業務改善を実践しながら理解するための教材です。

TABLE OF CONTENTS

目次

CONTENT

ここから

第34章では、完成アーキテクチャを整理しました。

フロントエンド、API、DB、Square、Webhook、管理画面、メール通知、デプロイ、運用がつながって、1つのECシステムになることを確認しました。

この章では、実際のNext.jsプロジェクトで、ファイルやフォルダをどのように分けるかを整理します。

ディレクトリ構成とは、プロジェクトのフォルダ設計のことです。

どのファイルを
どのフォルダに置くか
どんな名前にするか
どう分けるか

最初は小さなプロジェクトでも、商品管理、注文管理、Webhook、管理画面、メール通知、会員機能などが増えると、ファイル数が一気に増えます。

最初:
数ファイル

開発が進む:
数十ファイル

本格運用:
数百ファイル

フォルダ設計が曖昧だと、どこに何があるか分からなくなります。

攻略本風に言えば、第35章は**「巨大なダンジョンでも迷わないためのマップ作りステージ」**です。


35.1 appディレクトリ

appディレクトリは、Next.js App Routerの中心です。

ページ、レイアウト、API Route、管理画面など、URLに関係するものを置きます。

app/
  page.tsx
  layout.tsx
  products/
  cart/
  checkout/
  admin/
  api/

基本の考え方は、URLとフォルダ構成を対応させることです。

/products
  ↓
app/products/page.tsx

/products/classic-set
  ↓
app/products/[slug]/page.tsx

/admin/orders
  ↓
app/admin/orders/page.tsx

ECサイトのapp構成例です。

app/
  layout.tsx
  page.tsx

  products/
    page.tsx
    [slug]/
      page.tsx

  cart/
    page.tsx

  checkout/
    page.tsx

  thanks/
    page.tsx

  faq/
    page.tsx

  legal/
    tokushoho/
      page.tsx
    privacy/
      page.tsx
    cancel-policy/
      page.tsx

  admin/
    layout.tsx
    page.tsx
    orders/
      page.tsx
      [orderId]/
        page.tsx
    products/
      page.tsx
    inventory/
      page.tsx

  api/
    checkout/
      create-payment-link/
        route.ts
    webhooks/
      square/
        route.ts

appディレクトリには、主に次のものを置きます。

・ページ
・レイアウト
・ローディング画面
・エラー画面
・API Route
・管理画面のルート

ページコンポーネントは、できるだけ薄くします。

つまり、ページの中にすべての処理を書きすぎないようにします。

悪い例:
page.tsxに商品取得、計算、UI、送信処理を全部書く

よい例:
page.tsxは画面の組み立て役にする

page.tsxでは、必要なデータを取得して、コンポーネントへ渡します。

import { ProductList } from '@/components/products/ProductList';
import { getPublishedProducts } from '@/lib/products/getPublishedProducts';

/**
 * 役割: 商品一覧ページを表示する
 * 入力: なし
 * 出力: 公開中の商品一覧ページ
 */
export default async function ProductsPage() {
  const products = await getPublishedProducts();

  return <ProductList products={products} />;
}

appディレクトリの基本方針は、次の通りです。

URLに関係するものを置く
ページは薄くする
処理はlibやactionsへ逃がす
UIはcomponentsへ分ける

35.2 componentsディレクトリ

componentsディレクトリには、画面で使うUI部品を置きます。

ボタン、カード、商品一覧、ヘッダー、フッター、注文一覧などです。

components/
  ui/
  layout/
  products/
  cart/
  checkout/
  admin/

componentsは、画面の部品倉庫です。

商品カード
購入ボタン
カート一覧
注文ステータスバッジ
管理画面テーブル

構成例です。

components/
  ui/
    Button.tsx
    Input.tsx
    Badge.tsx
    Modal.tsx

  layout/
    Header.tsx
    Footer.tsx
    MobileNav.tsx

  products/
    ProductCard.tsx
    ProductList.tsx
    ProductDetail.tsx
    ProductPrice.tsx

  cart/
    CartItem.tsx
    CartSummary.tsx

  checkout/
    CheckoutButton.tsx
    OrderSummary.tsx

  admin/
    AdminSidebar.tsx
    OrderTable.tsx
    OrderStatusBadge.tsx
    ProductForm.tsx

uiには、汎用的な部品を置きます。

Button
Input
Select
Modal
Badge
Card

productsには、商品に関係する部品を置きます。

ProductCard
ProductList
ProductDetail
ProductGallery

adminには、管理画面専用の部品を置きます。

OrderTable
ProductForm
InventoryTable
AdminHeader

コンポーネントを分けるときの基準です。

同じUIを2回以上使う
  ↓
componentsに分ける

1つのpage.tsxが長くなりすぎる
  ↓
componentsに分ける

役割がはっきりしている画面部品
  ↓
componentsに分ける

悪い例です。

page.tsxの中に
商品カード
価格表示
購入ボタン
FAQ
関連商品
全部を書く

よい例です。

page.tsx
  ↓
ProductDetail
  ↓
ProductGallery
ProductPrice
AddToCartButton
ProductFaq
RelatedProducts

コンポーネントの基本方針は、次の通りです。

見た目の部品を置く
処理を書きすぎない
再利用できる単位で分ける
名前で役割が分かるようにする

35.3 libディレクトリ

libディレクトリには、アプリの共通処理を置きます。

DB接続、Square連携、メール送信、商品取得、注文処理などです。

lib/
  db/
  square/
  products/
  orders/
  inventory/
  emails/
  auth/

libは、ECシステムの裏側の道具箱です。

DBから商品を取る
注文を作る
Square決済リンクを作る
在庫を減らす
メールを送る

構成例です。

lib/
  db/
    client.ts
    admin.ts

  square/
    client.ts
    createPaymentLink.ts
    verifyWebhookSignature.ts

  products/
    getPublishedProducts.ts
    getProductBySlug.ts

  orders/
    createPendingOrder.ts
    markOrderAsPaid.ts
    getOrderById.ts

  inventory/
    decrementStock.ts
    getAvailableStock.ts

  emails/
    sendOrderConfirmationEmail.ts
    sendAdminOrderNotification.ts

  auth/
    getCurrentUser.ts
    requireAdmin.ts

たとえば、商品取得処理はlib/productsに置きます。

import { db } from '@/lib/db/client';
import type { Product } from '@/types/product';

/**
 * 役割: 公開中の商品一覧を取得する
 * 入力: なし
 * 出力: 公開中の商品配列
 */
export async function getPublishedProducts(): Promise<Product[]> {
  const products = await db.product.findMany({
    where: {
      isPublished: true,
    },
    orderBy: {
      displayOrder: 'asc',
    },
  });

  return products;
}

Square連携はlib/squareにまとめます。

import { squareClient } from '@/lib/square/client';

type CreateSquarePaymentLinkInput = {
  orderId: string;
  amount: number;
  redirectUrl: string;
};

/**
 * 役割: Squareの決済リンクを作成する
 * 入力: 注文ID、金額、決済後の戻り先URL
 * 出力: Square決済URL
 */
export async function createSquarePaymentLink(
  input: CreateSquarePaymentLinkInput
): Promise<string> {
  // 実際の実装ではSquare APIを呼び出す
  const checkoutUrl = `https://example.com/checkout/${input.orderId}`;

  return checkoutUrl;
}

libに置くべきものです。

・DBアクセス
・外部API連携
・メール送信
・認証チェック
・注文処理
・在庫処理
・共通計算処理

libに置かない方がよいものです。

・ページそのもの
・見た目だけのUI
・一時的な画面状態

libの基本方針は、次の通りです。

画面から独立した処理を置く
再利用できる処理を置く
外部サービス連携をまとめる
テストしやすくする

35.4 typesディレクトリ

typesディレクトリには、TypeScriptの型定義を置きます。

型とは、データの形を決める設計図です。

商品データの形
注文データの形
顧客データの形
在庫データの形

構成例です。

types/
  product.ts
  order.ts
  customer.ts
  inventory.ts
  coupon.ts
  square.ts
  admin.ts

商品型の例です。

export type ProductStatus = 'draft' | 'published' | 'archived';

export type Product = {
  id: string;
  name: string;
  slug: string;
  description: string;
  price: number;
  status: ProductStatus;
  stock: number;
  createdAt: string;
};

注文型の例です。

export type OrderStatus =
  | 'pending'
  | 'paid'
  | 'preparing'
  | 'shipped'
  | 'cancelled'
  | 'manual_review';

export type Order = {
  id: string;
  orderNumber: string;
  status: OrderStatus;
  totalAmount: number;
  customerEmail: string;
  createdAt: string;
};

型を分けるメリットです。

・データの形が分かりやすい
・入力ミスに気づきやすい
・関数の入出力が明確になる
・他のファイルから再利用できる

型をpage.tsxやコンポーネントの中に毎回書くと、重複しやすくなります。

悪い例:
Product型を複数ファイルで別々に定義

よい例:
types/product.tsにまとめる

型を使うと、関数の役割も明確になります。

import type { Order } from '@/types/order';

/**
 * 役割: 注文が発送可能か判定する
 * 入力: 注文情報
 * 出力: 発送可能ならtrue
 */
export function canShipOrder(order: Order): boolean {
  return order.status === 'paid' || order.status === 'preparing';
}

typesの基本方針です。

データの形を置く
アプリ全体で共有する型を置く
DB、API、UIで使い回す型を置く

型は、プロジェクトの共通言語です。


35.5 schemasディレクトリ

schemasディレクトリには、入力値の検証ルールを置きます。

フォーム入力やAPIへのリクエストは、そのまま信用してはいけません。

メールアドレスが正しいか
数量が1以上か
必須項目が入っているか
価格が不正でないか

schemasでは、Zodなどを使って入力値を検証する設計がよく使われます。

構成例です。

schemas/
  productSchema.ts
  checkoutSchema.ts
  addressSchema.ts
  couponSchema.ts
  adminOrderSchema.ts

購入フォームのスキーマ例です。

import { z } from 'zod';

export const checkoutSchema = z.object({
  email: z.string().email(),
  customerName: z.string().min(1),
  phoneNumber: z.string().min(10),
  postalCode: z.string().min(7),
  prefecture: z.string().min(1),
  city: z.string().min(1),
  addressLine1: z.string().min(1),
  addressLine2: z.string().nullable(),
});

export type CheckoutInput = z.infer<typeof checkoutSchema>;

クーポン入力のスキーマ例です。

import { z } from 'zod';

export const applyCouponSchema = z.object({
  code: z.string().min(1).max(50),
  cartId: z.string().uuid(),
});

export type ApplyCouponInput = z.infer<typeof applyCouponSchema>;

スキーマを使う場所です。

・フォーム送信時
・API受信時
・Server Actions
・管理画面の登録処理
・Webhook以外の外部入力

スキーマを使うメリットです。

・入力ミスを防げる
・不正な値を止められる
・エラー表示を作りやすい
・型と検証をつなげられる

重要なのは、フロントエンドだけでなくサーバー側でも検証することです。

ブラウザ側のバリデーション:
便利だが回避される可能性あり

サーバー側のバリデーション:
必須

schemasの基本方針です。

入力値のルールを置く
フォームとAPIで同じルールを使う
不正な値をサーバー側で止める

スキーマは、ECシステムの門番です。


35.6 actionsディレクトリ

actionsディレクトリには、画面から呼び出す処理を置きます。

Next.jsのServer Actionsを使う場合、フォーム送信や管理画面操作をactionsにまとめると分かりやすくなります。

actions/
  cart/
  checkout/
  admin/
  products/
  orders/

actionsは、ユーザー操作から始まる処理を置く場所です。

カートに追加する
注文を作る
商品を登録する
注文ステータスを変更する
クーポンを適用する

構成例です。

actions/
  cart/
    addCartItem.ts
    removeCartItem.ts

  checkout/
    createCheckoutSession.ts
    applyCoupon.ts

  admin/
    updateOrderStatus.ts
    updateProduct.ts
    createProduct.ts

  account/
    updateAddress.ts

注文ステータス変更の例です。

'use server';

import { requireAdmin } from '@/lib/auth/requireAdmin';
import { updateOrderStatus } from '@/lib/orders/updateOrderStatus';
import type { OrderStatus } from '@/types/order';

type UpdateOrderStatusActionInput = {
  orderId: string;
  status: OrderStatus;
};

/**
 * 役割: 管理者操作により注文ステータスを更新する
 * 入力: 注文IDと変更後ステータス
 * 出力: 更新結果
 */
export async function updateOrderStatusAction(
  input: UpdateOrderStatusActionInput
): Promise<{ success: boolean }> {
  await requireAdmin();

  await updateOrderStatus({
    orderId: input.orderId,
    status: input.status,
  });

  return { success: true };
}

actionslibの違いです。

actions:
画面操作から呼ばれる入口

lib:
実際の共通処理

たとえば、管理画面から注文ステータスを変更する場合です。

管理画面のボタン
  ↓
action
  ↓
lib/orders/updateOrderStatus
  ↓
DB更新

actionsにすべての処理を書くと、再利用しにくくなります。

悪い例:
actionの中にDB処理、メール送信、ログ記録を全部書く

よい例:
actionは入口
実処理はlibへ分ける

actionsの基本方針です。

ユーザー操作の入口を置く
認証チェックを行う
入力検証を行う
実処理はlibへ渡す

35.7 constantsディレクトリ

constantsディレクトリには、アプリ全体で使う固定値を置きます。

固定値とは、頻繁には変わらない設定や表示文言のことです。

送料
注文ステータス
配送方法
管理画面メニュー
会社情報
URL

構成例です。

constants/
  routes.ts
  orderStatus.ts
  shipping.ts
  site.ts
  adminMenu.ts
  legal.ts

サイト情報の例です。

export const SITE_CONFIG = {
  name: '金七商店オンラインストア',
  description: '本枯節とだしのオンラインストア',
  url: 'https://example.com',
} as const;

注文ステータス表示の例です。

import type { OrderStatus } from '@/types/order';

export const ORDER_STATUS_LABELS: Record<OrderStatus, string> = {
  pending: '決済待ち',
  paid: '決済完了',
  preparing: '発送準備中',
  shipped: '発送済み',
  cancelled: 'キャンセル',
  manual_review: '手動確認',
};

送料設定の例です。

export const SHIPPING_CONFIG = {
  defaultFee: 800,
  freeShippingThreshold: 10000,
} as const;

固定値を各ファイルに直接書くと、変更が大変になります。

悪い例:
送料無料条件 10,000円を複数ファイルに直接書く

よい例:
constants/shipping.tsにまとめる

これにより、変更時に1か所直せば済みます。

送料無料条件を変更
  ↓
constantsを変更
  ↓
関連箇所に反映

constantsに置くべきものです。

・表示ラベル
・ステータス名
・固定メニュー
・配送設定
・サイト基本情報
・共通URL

constantsに置かない方がよいものです。

・秘密情報
・APIキー
・Access Token
・頻繁にDBで変更したい商品価格

秘密情報は環境変数に置きます。

商品価格や在庫のように管理画面から変更したいものはDBに置きます。

constants:
固定値

DB:
運用で変わる値

環境変数:
秘密情報や環境ごとの値

35.8 testsディレクトリ

testsディレクトリには、テストコードを置きます。

ECでは、決済、在庫、注文ステータス、クーポンなど、壊れると困る処理があります。

金額計算
在庫更新
クーポン適用
Webhook処理
注文ステータス変更

これらはテストを書いておくと安心です。

構成例です。

tests/
  unit/
    calculateCartTotal.test.ts
    applyCoupon.test.ts
    decrementStock.test.ts

  integration/
    createOrder.test.ts
    webhookPaymentUpdated.test.ts

  e2e/
    checkout.spec.ts
    admin-order-flow.spec.ts

テストには種類があります。

unit:
小さな関数のテスト

integration:
DBやAPIを含む流れのテスト

e2e:
実際のユーザー操作に近いテスト

金額計算のテスト例です。

type CartItem = {
  price: number;
  quantity: number;
};

/**
 * 役割: カート内商品の小計を計算する
 * 入力: カート商品配列
 * 出力: 小計金額
 */
function calculateSubtotal(items: CartItem[]): number {
  return items.reduce((total, item) => {
    return total + item.price * item.quantity;
  }, 0);
}

// テストイメージ
const subtotal = calculateSubtotal([
  { price: 1000, quantity: 2 },
  { price: 500, quantity: 1 },
]);

console.log(subtotal); // 2500

特にテストしたい処理です。

・商品価格の合計計算
・送料計算
・送料無料条件
・クーポン適用
・在庫不足判定
・Webhookの重複処理
・注文ステータス変更

Webhookのテストでは、同じイベントが2回来ても二重処理されないことを確認します。

1回目:
在庫を1減らす

2回目:
処理済みなのでスキップ

結果:
在庫は1回分だけ減る

testsの基本方針です。

壊れると困る処理を優先する
金額・在庫・決済・Webhookを重点的に見る
画面よりもまず重要ロジックを守る

テストは、ECの保険です。

全部を完璧にテストするより、壊れると売上や信頼に直結する部分から守ります。


35.9 命名規則

命名規則とは、ファイル名、関数名、変数名の付け方のルールです。

名前が分かりやすいと、コードを読むのが楽になります。

何をするファイルか
何をする関数か
何の値か

ファイル名の基本です。

コンポーネント:
ProductCard.tsx
OrderTable.tsx

関数ファイル:
getProductBySlug.ts
createPendingOrder.ts

型定義:
product.ts
order.ts

スキーマ:
checkoutSchema.ts
couponSchema.ts

関数名は、動詞から始めると分かりやすいです。

getProductBySlug
createPendingOrder
markOrderAsPaid
decrementStock
sendOrderConfirmationEmail

悪い名前の例です。

data
handle
process
doThing
func1

よい名前の例です。

product
order
handleCheckoutSubmit
createPaymentLink
verifySquareWebhookSignature

命名では、略しすぎないことも大切です。

悪い例:
prd
ord
usr
calcAmt

よい例:
product
order
user
calculateAmount

Booleanの変数は、true/falseが分かる名前にします。

isPublished
isPaid
hasStock
canShip
shouldSendEmail

ステータス名は、プロジェクト全体で統一します。

pending
paid
preparing
shipped
cancelled
manual_review

同じ意味なのに別の名前を使うと混乱します。

悪い例:
paid
payment_completed
completed_payment

よい例:
paid に統一

命名規則の基本方針です。

略しすぎない
役割が分かる名前にする
同じ意味には同じ言葉を使う
関数名は動詞から始める
Booleanはis/has/can/shouldを使う

名前は、未来の自分への説明です。


35.10 保守しやすい分割方針

最後に、保守しやすい分割方針を整理します。

保守しやすいとは、後から直しやすいということです。

ECは公開後も変更が続きます。

商品を増やす
価格を変える
送料を変える
キャンペーンを追加する
管理画面を改善する
決済処理を修正する

そのため、最初からすべてを1つのファイルに詰め込まないようにします。

悪い例:
page.tsxに全部書く

よい例:
画面、処理、型、検証、固定値を分ける

分割の基本です。

app:
URLとページ

components:
UI部品

lib:
共通処理

types:
型定義

schemas:
入力検証

actions:
画面操作の入口

constants:
固定値

tests:
テスト

1つのファイルが長くなりすぎたら、分割を考えます。

目安:
1つのファイルに複数の役割がある
読みながらスクロールが多い
同じ処理を別の場所でも使いたい
テストを書きたい

分割するときは、役割ごとに分けます。

商品を取得する処理
  ↓
lib/products/getProductBySlug.ts

商品を表示するUI
  ↓
components/products/ProductDetail.tsx

商品型
  ↓
types/product.ts

商品入力検証
  ↓
schemas/productSchema.ts

悪い分け方は、意味なく細かくしすぎることです。

細かすぎる分割:
1行の関数まで全部別ファイル

問題:
逆に探しにくい

よい分け方です。

役割が違うものを分ける
再利用するものを分ける
テストしたいものを分ける
変更頻度が違うものを分ける

保守しやすい構成では、変更箇所が分かりやすくなります。

送料を変えたい
  ↓
constants/shipping.ts

注文ステータスを変えたい
  ↓
types/order.ts
  ↓
constants/orderStatus.ts

Square連携を直したい
  ↓
lib/square/

プロジェクト全体の完成例です。

app/
components/
lib/
types/
schemas/
actions/
constants/
tests/
public/

さらに本格的にすると、次のようになります。

app/
  products/
  cart/
  checkout/
  admin/
  api/

components/
  ui/
  layout/
  products/
  cart/
  checkout/
  admin/

lib/
  db/
  square/
  products/
  orders/
  inventory/
  emails/
  auth/

types/
schemas/
actions/
constants/
tests/
public/

保守しやすい分割方針は、次の一言にまとめられます。

どこに何があるか、名前を見れば分かる状態にする。

第35章のまとめ

この章では、Next.jsでECサイトを作るときのディレクトリ構成を整理しました。

プロジェクトが大きくなるほど、どこに何を書くかのルールが重要になります。

この章で覚えておきたいポイントは、次の通りです。

1. appディレクトリには、URLに関係するページやAPIを置く
2. componentsディレクトリには、画面で使うUI部品を置く
3. libディレクトリには、DB、Square、メールなどの共通処理を置く
4. typesディレクトリには、TypeScriptの型定義を置く
5. schemasディレクトリには、入力値の検証ルールを置く
6. actionsディレクトリには、画面操作から呼ばれる処理を置く
7. constantsディレクトリには、固定値や表示ラベルを置く
8. testsディレクトリには、金額、在庫、Webhookなど重要処理のテストを置く
9. 命名規則では、役割が分かる名前を付ける
10. 保守しやすい分割では、画面、処理、型、検証、固定値を分ける

ディレクトリ構成の基本は、次の一言にまとめられます。

フォルダ構成は、未来の自分とチームへの案内板である。

今は自分だけが分かればよくても、数か月後には忘れます。

チーム開発では、他の人も読みます。

だからこそ、最初から分かりやすい場所に、分かりやすい名前で置く。

これが、長く運用できるECシステムの土台になります。

第36章では、この構成をもとに、実際に制作案件としてどう提案し、価格設計や運用契約につなげるかを整理していきます。

FAQ

よくある質問

ディレクトリ構成は医療関係者向けだけの内容ですか。
医療分野の例が含まれる場合もありますが、医療関係者だけに限定した内容ではありません。生成AI、AI活用、DX、業務改善、プロトタイプ開発など、一般的なAI学習の事例として読める内容です。
AI初心者でも読めますか。
はい。AIをこれから学ぶ方、数学が苦手な方、仕事でAIを使いたい方にも読み進めやすいように、教材の章と節の流れに沿って整理しています。
サムネイル画像は必ず表示されますか。
はい。教材にcoverUrlが設定されている場合はその画像を表示し、未設定の場合は代替サムネイル画像を表示します。
Square API × Next.js 実践EC開発|自社ECを低コストで作る、決済・在庫・注文管理の設計と実装のほかの章も読めますか。
はい。教材トップから章立てを確認でき、前後の節へもページ下部のナビゲーションから移動できます。