0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

画像管理の負債をなくそう!コンポーネント設計を応用した画像ディレクトリ構成術

0
Posted at

はじめに

画像の管理って、あまり重要視されていませんよね…🤔?

コンポーネントのディレクトリ設計は割と意識されている印象ですが、画像のディレクトリ構成は後回しにされがちだと感じています…💋

🤪「とりあえず/imagesフォルダに放り込んでおこう」
🥴「ファイル名は適当でいいや」

こんな風に始まったプロジェクトが、時間が経つにつれて:

  • 🔄 似たような画像が複数存在する
  • 🗑️ 古い画像が残っていて削除できない
  • 🔍 必要な画像がどこにあるか分からない
  • 📱 デバイス別の画像が混在している

運用期間が長くなるほど、技術負債はどんどん大きくなっていきます💸

今回は、コンポーネント設計の概念を画像管理に応用した、持続可能なディレクトリ構成を考えてみたいと思います🚀

現状:よくある画像管理の問題

典型的な問題パターン

多くのプロジェクトで見られる画像管理の問題を整理してみました:

1. フラットな構造による混乱

public/
├── image1.png
├── image2.jpg
├── logo.svg
├── hero-desktop.jpg
├── hero-mobile.jpg
├── icon-user.svg
├── icon-menu.svg
├── background.png
├── banner.jpg
└── ...(100個以上のファイル)

問題点:

  • どの画像がどこで使われているか分からない
  • 似たような名前のファイルが乱立
  • 削除していい画像の判断ができない

2. 命名規則の混在

// 命名ルールがバラバラ
hero_image.jpg        // スネークケース
heroImage.png         // キャメルケース  
hero-image-mobile.jpg // ケバブケース
HeroImg.svg          // パスカルケース

問題点:

  • 命名規則が統一されていない
  • 検索しづらい
  • チーム開発で混乱を招く

3. 責任の曖昧さ

// どのコンポーネントの画像?
button-icon.svg     // どのボタン?
card-background.jpg // どのカード?
image.png          // 何の画像?

問題点:

  • 画像の使用箇所が特定できない
  • リファクタリング時に影響範囲が分からない
  • 未使用画像の特定が困難

解決方法:コンポーネント指向の画像管理

コンポーネント設計で重視される責任の分離再利用性の概念を、画像管理にも適用してみましょう🍟!

💡 参考: モダン開発のディレクトリ構成設計!中大規模プロジェクトでも破綻しない実践的なファイル管理術も併せてご覧ください🚀

基本コンセプト

  1. 機能別の分類:用途に応じてディレクトリを分割
  2. 責任の明確化:どのコンポーネント/ページで使うかを明示
  3. 命名規則の統一:予測可能で検索しやすい名前
  4. スコープの管理:グローバル vs ローカルな画像の区別

推奨ディレクトリ構成

public/
├── images/
│   ├── global/              # サイト全体で使用
│   │   ├── logos/
│   │   │   ├── logo.svg
│   │   │   ├── logo-dark.svg
│   │   │   └── logo-small.svg
│   │   ├── icons/
│   │   │   ├── arrow-right.svg
│   │   │   ├── check.svg
│   │   │   └── close.svg
│   │   └── backgrounds/
│   │       ├── texture.png
│   │       └── pattern.svg
│   │
│   ├── components/          # 特定コンポーネント用  
│   │   ├── header/
│   │   │   ├── logo-header.svg
│   │   │   ├── menu-icon.svg
│   │   │   └── nav-bg.png
│   │   ├── hero/
│   │   │   ├── hero-desktop.jpg
│   │   │   ├── hero-tablet.jpg
│   │   │   └── hero-mobile.jpg
│   │   ├── cards/
│   │   │   ├── placeholder.jpg
│   │   │   └── default-thumbnail.jpg
│   │   ├── buttons/
│   │   │   ├── primary-bg.svg
│   │   │   └── icon-arrow.svg
│   │   └── footer/
│   │       ├── social-links-bg.png
│   │       └── copyright-logo.svg
│   │
│   └── pages/               # 特定ページ用
│       ├── home/
│       │   ├── feature-showcase.jpg
│       │   ├── testimonial-bg.jpg
│       │   └── cta-banner.png
│       ├── about/
│       │   ├── team-photo.jpg
│       │   ├── office-exterior.jpg
│       │   └── company-history.png
│       └── contact/
│           ├── map-bg.png
│           └── office-interior.jpg
├── fonts/                   # フォントファイル
├── videos/                  # 動画ファイル
└── icons/                   # ファビコンなど

命名規則の統一

基本ルール

すべての画像ファイルで共通して守るべき基本ルールを定めましょう:

命名の原則:

  • ケバブケースで統一:単語を-で区切る(user-profile.svg
  • 機能・用途を明示:何に使うかが分かる名前にする
  • バリエーションを区別:サイズや状態の違いを明確に表現
  • 曖昧な名前を避けるimage1.pngbg.jpg などは禁止

用途別の命名

グローバル画像の命名

サイト全体で使用される画像は、用途と種類を明確に表す名前にしましょう:

// 用途別の明確な命名
arrow-right.svg        ✅ OK: 右向き矢印アイコン
check-circle.svg       ✅ OK: チェックマーク(円形)
user-profile.svg       ✅ OK: ユーザープロフィール用
logo-dark.svg          ✅ OK: ダークテーマ用ロゴ
background-texture.png ✅ OK: テクスチャ背景

// 避けるべき曖昧な命名
icon1.svg              ❌ NG: 何のアイコン?
image.png              ❌ NG: 用途が不明
bg.jpg                 ❌ NG: どの背景?
コンポーネント固有画像の命名
// components/cards/
placeholder.jpg        ✅ OK: そのコンポーネント内でのみ使用
default-thumbnail.jpg  ✅ OK: 文脈が明確
icon.svg              ✅ OK: スコープが限定されている

// components/buttons/
primary-bg.svg        ✅ OK: ボタンの背景画像
icon-arrow.svg        ✅ OK: ボタンに使用するアイコン

レスポンシブ画像の管理

レスポンシブ画像は、画像管理の中でも特に複雑になりやすい部分です。チーム内での統一ルールが最も重要なポイントとなります。

命名パターンの選択

レスポンシブ画像の管理には、以下のアプローチがあります:

ファイル名にデバイス識別子を含ませる

hero-desktop.jpg       ✅ おすすめ
hero-tablet.jpg        ✅ おすすめ 
hero-mobile.jpg        ✅ おすすめ

解像度別(Retina対応)

logo@1x.svg           ✅ おすすめ
logo@2x.svg           ✅ おすすめ
logo@3x.svg           ✅ おすすめ

推奨ディレクトリ構成では、ファイル名にデバイス識別子を含ませる方法を採用します。

理由:

  • 同一ディレクトリ内で管理できる
  • ファイル名から用途が一目で分かる
  • TypeScriptの型定義もシンプル
  • 一覧性が高い(ls コマンドで関連ファイルが並ぶ)

デバイス識別子の統一

ファイル名にデバイス識別子を含ませる際は、識別子を統一しましょう:

// 統一された命名規則
hero-desktop.jpg      ✅ 明確にデスクトップ用
hero-mobile.jpg       ✅ 明確にモバイル用
hero-tablet.jpg       ✅ 明確にタブレット用

// 避けるべき混乱した命名
hero-pc.jpg           ❌ desktop? pc? 曖昧
hero-sp.jpg           ❌ mobile? smartphone? 不明瞭
hero-large.jpg        ❌ どのサイズが基準?
hero-small.jpg        ❌ 相対的で分かりにくい

統一ルールの重要性

個人の好みや「何となく」での配置ではなく、明確なルールに基づいて全員が同じ方針で管理することで:

  • 迷いのない開発:どこに置くか悩む時間がゼロ
  • 一貫性のある構造:誰が見ても理解しやすい
  • 属人化の防止:担当者が変わっても迷わない
  • レビュー効率向上:ルール違反をすぐに指摘できる

ルール統一の効果:

  • デバイス別ファイルの対応関係が一目瞭然
  • ファイル名からブレイクポイントが予測可能
  • 未作成のサイズバリエーションがすぐに分かる

重要なのは一貫性
デバイス識別子を統一し、チーム全員が同じルールに従うことが成功の鍵です🔑

実装例:Next.jsでの活用

画像パスの型安全性を確保

実際のプロジェクトでは直接 src="/images/hero.jpg" と指定することが多く、ここまでやるのは正直 過剰な気もしつつ…🥹

型安全性を重視するなら以下のようなメリットもあります:

  • コンパイル時エラー検出:存在しない画像パスをビルド前に発見
  • IDE支援:自動補完やリファクタリング時の安全性向上
  • チーム開発の効率化:画像パスの入力ミスを防止

大規模プロジェクトや、画像管理が複雑になってきた場合の選択肢として参考にしてください💐

// types/images.ts
export const ImagePaths = {
  global: {
    logos: {
      main: '/images/global/logos/logo.svg',
      dark: '/images/global/logos/logo-dark.svg',
      small: '/images/global/logos/logo-small.svg',
    },
    icons: {
      arrowRight: '/images/global/icons/arrow-right.svg',
      check: '/images/global/icons/check.svg',
      close: '/images/global/icons/close.svg',
    },
  },
  components: {
    header: {
      logo: '/images/components/header/logo-header.svg',
      menuIcon: '/images/components/header/menu-icon.svg',
      navBackground: '/images/components/header/nav-bg.png',
    },
    hero: {
      desktop: '/images/components/hero/hero-desktop.jpg',
      tablet: '/images/components/hero/hero-tablet.jpg',
      mobile: '/images/components/hero/hero-mobile.jpg',
    },
    cards: {
      placeholder: '/images/components/cards/placeholder.jpg',
    },
    buttons: {
      primaryBg: '/images/components/buttons/primary-bg.svg',
      iconArrow: '/images/components/buttons/icon-arrow.svg',
    },
  },
  pages: {
    home: {
      featureShowcase: '/images/pages/home/feature-showcase.jpg',
      testimonialBg: '/images/pages/home/testimonial-bg.jpg',
    },
    about: {
      teamPhoto: '/images/pages/about/team-photo.jpg',
      officeExterior: '/images/pages/about/office-exterior.jpg',
    },
  },
} as const

export type ImagePath = 
  | typeof ImagePaths.global.logos[keyof typeof ImagePaths.global.logos]
  | typeof ImagePaths.global.icons[keyof typeof ImagePaths.global.icons]
  | typeof ImagePaths.components.header[keyof typeof ImagePaths.components.header]
  | typeof ImagePaths.components.hero[keyof typeof ImagePaths.components.hero]
  | typeof ImagePaths.components.cards[keyof typeof ImagePaths.components.cards]
  | typeof ImagePaths.pages.home[keyof typeof ImagePaths.pages.home]
  | typeof ImagePaths.pages.about[keyof typeof ImagePaths.pages.about]

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

// components/Header.tsx
import { ImagePaths } from '@/types/images'
import Image from 'next/image'

export const Header = () => {
  return (
    <header>
      <Image 
        src={ImagePaths.components.header.logo}
        alt="Header Logo"
        width={120}
        height={40}
      />
      
      <nav style={{ backgroundImage: `url(${ImagePaths.components.header.navBackground})` }}>
        <button>
          <Image
            src={ImagePaths.components.header.menuIcon}
            alt="Menu"
            width={24}
            height={24}
          />
        </button>
      </nav>
    </header>
  )
}

// components/Hero.tsx
export const Hero = () => {
  return (
    <section>
      <picture>
        <source 
          media="(min-width: 1024px)" 
          srcSet={ImagePaths.components.hero.desktop} 
        />
        <source 
          media="(min-width: 768px)" 
          srcSet={ImagePaths.components.hero.tablet} 
        />
        <img 
          src={ImagePaths.components.hero.mobile}
          alt="Hero image" 
        />
      </picture>
    </section>
  )
}

カスタムフック for レスポンシブ画像管理

レスポンシブ画像の管理ロジックが複雑になってきた場合、カスタムフックで共通化できます。

このカスタムフックが解決すること:

  • デバイス別画像パスの取得ロジックをコンポーネント間で再利用
  • Next.js の getImageProps 設定を自動生成
  • ブレークポイントやサイズ設定を一箇所で管理

💡 使用場面:
複数のコンポーネントで同じレスポンシブ画像パターンを使う場合や、画像設定をプログラマブルに制御したい場合に有用です🥑!

// hooks/useResponsiveImages.ts
import { ImagePaths } from '@/types/images'

type DeviceType = 'desktop' | 'tablet' | 'mobile'
type ResponsiveImageConfig = {
  desktop: string
  tablet?: string
  mobile: string
}

export const useResponsiveImages = () => {
  // レスポンシブ画像のパスを取得
  const getResponsiveImagePath = (
    config: ResponsiveImageConfig,
    device: DeviceType
  ): string => {
    if (device === 'tablet' && config.tablet) {
      return config.tablet
    }
    return device === 'desktop' ? config.desktop : config.mobile
  }

  // Next.js getImageProps用のレスポンシブ設定生成
  const getImagePropsConfig = (config: ResponsiveImageConfig) => {
    return {
      desktop: {
        src: config.desktop,
        width: 1200,
        height: 800,
        quality: 90,
      },
      tablet: config.tablet ? {
        src: config.tablet,
        width: 768,
        height: 1024,
        quality: 85,
      } : null,
      mobile: {
        src: config.mobile,
        width: 375,
        height: 812,
        quality: 80,
      },
    }
  }

  return {
    getResponsiveImagePath,
    getImagePropsConfig,
    // 特定コンポーネントのレスポンシブ画像設定
    heroImages: {
      desktop: ImagePaths.components.hero.desktop,
      tablet: ImagePaths.components.hero.tablet,
      mobile: ImagePaths.components.hero.mobile,
    },
  }
}

// 使用例
const { heroImages, getImagePropsConfig } = useResponsiveImages()
const heroConfig = getImagePropsConfig(heroImages)

レスポンシブ画像コンポーネントの実装例

上記で紹介した useResponsiveImages フックを活用したコンポーネント実装例です:

// components/ResponsiveImage.tsx
import { useResponsiveImages } from '@/hooks/useResponsiveImages'

type ResponsiveImageProps = {
  images: {
    desktop: string
    tablet?: string
    mobile: string
  }
  alt: string
  className?: string
  priority?: boolean
}

export const ResponsiveImage = ({ 
  images, 
  alt, 
  className = "",
  priority = false 
}: ResponsiveImageProps) => {
  const { getImagePropsConfig } = useResponsiveImages()
  const config = getImagePropsConfig(images)

  return (
    <picture>
      <source 
        media="(min-width: 1024px)" 
        srcSet={`${config.desktop.src} 1x`}
      />
      {config.tablet && (
        <source 
          media="(min-width: 768px)" 
          srcSet={`${config.tablet.src} 1x`}
        />
      )}
      <img 
        src={config.mobile.src}
        alt={alt}
        className={className}
        loading={priority ? "eager" : "lazy"}
      />
    </picture>
  )
}

// より簡単な使用例
export const SimpleResponsiveImage = ({ 
  basePath, 
  alt 
}: { 
  basePath: string
  alt: string 
}) => {
  const images = {
    desktop: `${basePath}-desktop.jpg`,
    tablet: `${basePath}-tablet.jpg`, 
    mobile: `${basePath}-mobile.jpg`,
  }

  return <ResponsiveImage images={images} alt={alt} />
}

使用例:

// カスタムフックを活用した使用パターン
const { heroImages } = useResponsiveImages()

<ResponsiveImage
  images={heroImages}
  alt="Hero image"
  priority={true}
/>

// シンプルなパターン
<SimpleResponsiveImage
  basePath="/images/components/hero/hero"
  alt="Hero image"
/>

運用のベストプラクティス

1. 画像追加時のチェックリスト

新しい画像を追加する際の確認項目です:

【配置・分類チェック】
□ 適切なディレクトリに配置されているか?
  - global/ : サイト全体で使用する画像
  - components/ : 特定コンポーネント専用の画像
  - pages/ : 特定ページ専用の画像
□ ディレクトリ名がコンポーネント名と正確に対応しているか?
□ 似たような画像が既に存在しないか?

【命名チェック】
□ ケバブケース(small-kebab-case)で統一されているか?
□ 用途・機能が名前から明確に分かるか?
□ 曖昧な名前(image1.png, bg.jpg等)を避けているか?

【レスポンシブ対応チェック(該当する場合)】
□ 必要なデバイスサイズが全て揃っているか?
  - Desktop: ○○-desktop.jpg
  - Tablet: ○○-tablet.jpg(必要に応じて)
  - Mobile: ○○-mobile.jpg
□ デバイス識別子が統一されているか?

【型定義更新(TypeScriptの場合)】
□ ImagePaths の型定義に追加されているか?
□ 関連する型定義が更新されているか?

2. 画像の整理とクリーンアップ

定期的な画像の棚卸しと不要ファイルの削除は、技術負債の蓄積を防ぐ重要な作業です:

#!/bin/bash
# 画像の使用状況チェックスクリプト

echo "=== 画像使用状況の調査 ==="

# 全画像ファイルの棚卸し
echo "📸 画像ファイル一覧の生成中..."
find public/images -name "*.jpg" -o -name "*.png" -o -name "*.svg" | sort > all-images.txt
echo "総画像数: $(wc -l < all-images.txt)"

# 未使用画像の検出
echo -e "\n🔍 未使用画像の検出中..."
while read -r image_file; do
  filename=$(basename "$image_file")
  
  # ソースコード内での使用確認
  if ! grep -r "$filename" src/ --include="*.tsx" --include="*.ts" --include="*.js" --include="*.jsx" > /dev/null 2>&1; then
    echo "❌ 未使用: $image_file"
  fi
done < all-images.txt

# レスポンシブ画像のセット完整性チェック
echo -e "\n📱 レスポンシブ画像セットのチェック中..."
find public/images -name "*-desktop.*" | while read desktop_file; do
  base_name=$(basename "$desktop_file" | sed 's/-desktop\./\./')
  dir_name=$(dirname "$desktop_file")
  mobile_file="${dir_name}/${base_name/-desktop/-mobile}"
  
  if [[ ! -f "$mobile_file" ]]; then
    echo "⚠️  不完全なセット: $desktop_file (モバイル版が存在しません)"
  fi
done

# ディレクトリ構成のチェック
echo -e "\n📁 ディレクトリ構成のチェック中..."
if [[ ! -d "public/images/global" ]]; then
  echo "⚠️  global/ ディレクトリが存在しません"
fi

if [[ ! -d "public/images/components" ]]; then
  echo "⚠️  components/ ディレクトリが存在しません"
fi

if [[ ! -d "public/images/pages" ]]; then
  echo "⚠️  pages/ ディレクトリが存在しません"
fi

# 命名規則違反のチェック
echo -e "\n📝 命名規則のチェック中..."
find public/images -name "*.jpg" -o -name "*.png" -o -name "*.svg" | while read file; do
  filename=$(basename "$file")
  
  # ケバブケース以外の命名をチェック
  if [[ "$filename" =~ [A-Z] ]] || [[ "$filename" =~ _ ]]; then
    echo "⚠️  命名規則違反: $file(キャメルケース・スネークケースは避けましょう)"
  fi
  
  # 曖昧な名前のチェック
  if [[ "$filename" =~ ^(image|img|pic|photo)[0-9]*\. ]] || [[ "$filename" =~ ^(bg|background)[0-9]*\. ]]; then
    echo "⚠️  曖昧な命名: $file(具体的な用途名をつけましょう)"
  fi
done

echo -e "\n✅ 画像使用状況の調査完了"

3. ESLintルールでの統制

コードレビューでの画像管理ルール違反を自動検出する設定例:

// .eslintrc.js
module.exports = {
  rules: {
    // 画像パスのハードコーディングを禁止
    'no-hardcoded-paths': 'error',
    // 直接的な画像参照の制限
    'prefer-image-constants': 'warn',
  },
  overrides: [
    {
      files: ['**/*.tsx', '**/*.ts'],
      rules: {
        // TypeScript環境での画像管理ルール
        'prefer-typed-image-paths': 'error',
        // alt属性の必須化
        'require-image-alt': 'error',
        // 画像のlazy loading推奨
        'prefer-lazy-loading': 'warn',
      },
    },
  ],
  // カスタムルールの例(実装は個別に必要)
  plugins: ['@custom/image-management'],
  '@custom/image-management': {
    'directory-structure': 'error',      // 正しいディレクトリ構成の強制
    'naming-convention': 'error',        // 命名規則の強制
    'responsive-completeness': 'warn',   // レスポンシブ画像セットの完整性
    'unused-images': 'warn',            // 未使用画像の警告
  }
}

実用的なカスタムルール例:

// eslint-plugin-local/rules/image-directory-check.js
module.exports = {
  meta: {
    docs: {
      description: "画像が適切なディレクトリに配置されているかチェック"
    }
  },
  
  create(context) {
    return {
      Literal(node) {
        if (typeof node.value === 'string' && node.value.match(/\.(jpg|png|svg)$/)) {
          const imagePath = node.value;
          
          // global/components/pagesディレクトリ以外の画像を検出
          if (!imagePath.includes('/global/') && 
              !imagePath.includes('/components/') && 
              !imagePath.includes('/pages/')) {
            context.report({
              node,
              message: '画像は global/, components/, pages/ のいずれかのディレクトリに配置してください'
            });
          }
          
          // 命名規則違反を検出
          const filename = imagePath.split('/').pop();
          if (filename.includes('_') || /[A-Z]/.test(filename)) {
            context.report({
              node,
              message: '画像ファイル名はケバブケース(小文字-ハイフン)で統一してください'
            });
          }
        }
      }
    };
  }
};

4. CI/CDでの画像管理チェック

プルリクエスト時に自動的に画像管理ルールをチェックする設定例:

# .github/workflows/image-check.yml
name: Image Management Check
on: [pull_request]

jobs:
  check-image-management:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
        with:
          fetch-depth: 0  # 変更ファイルの比較のため過去のコミットも取得
      
      - name: Check image directory structure
        run: |
          # 必要なディレクトリ構成をチェック
          echo "📁 ディレクトリ構成をチェック中..."
          
          required_dirs=("public/images/global" "public/images/components" "public/images/pages")
          for dir in "${required_dirs[@]}"; do
            if [[ ! -d "$dir" ]]; then
              echo "❌ エラー: $dir ディレクトリが存在しません"
              exit 1
            fi
          done
          
          echo "✅ ディレクトリ構成: OK"
      
      - name: Check image naming convention
        run: |
          # 画像ファイルの命名規則をチェック
          echo "📝 命名規則をチェック中..."
          
          violations=0
          while read -r file; do
            filename=$(basename "$file")
            
            # ケバブケース以外の命名をチェック
            if [[ "$filename" =~ [A-Z_] ]]; then
              echo "❌ 命名規則違反: $file (ケバブケースを使用してください)"
              violations=$((violations + 1))
            fi
            
            # 曖昧な名前をチェック
            if [[ "$filename" =~ ^(image|img|pic|bg)[0-9]*\. ]]; then
              echo "⚠️  曖昧な命名: $file (具体的な用途名をつけてください)"
            fi
          done < <(find public/images -name "*.jpg" -o -name "*.png" -o -name "*.svg")
          
          if [[ $violations -gt 0 ]]; then
            echo "❌ 命名規則違反が ${violations} 件見つかりました"
            exit 1
          fi
          
          echo "✅ 命名規則: OK"
      
      - name: Check for unused images
        run: |
          # 未使用画像をチェック
          echo "🔍 未使用画像をチェック中..."
          
          unused_count=0
          while read -r image_file; do
            filename=$(basename "$image_file")
            
            # ソースコード内での使用確認
            if ! grep -r "$filename" src/ --include="*.tsx" --include="*.ts" --include="*.js" --include="*.jsx" > /dev/null 2>&1; then
              echo "⚠️  未使用の可能性: $image_file"
              unused_count=$((unused_count + 1))
            fi
          done < <(find public/images -name "*.jpg" -o -name "*.png" -o -name "*.svg")
          
          if [[ $unused_count -gt 0 ]]; then
            echo "⚠️  ${unused_count} 個の未使用画像候補が見つかりました(要確認)"
          else
            echo "✅ 未使用画像: 見つかりませんでした"
          fi
      
      - name: Check responsive image sets
        run: |
          # レスポンシブ画像セットの整合性をチェック
          echo "📱 レスポンシブ画像セットをチェック中..."
          
          incomplete_sets=0
          while read -r desktop_file; do
            base_name=$(echo "$desktop_file" | sed 's/-desktop\././')
            mobile_file=$(echo "$desktop_file" | sed 's/-desktop\./-mobile\./')
            
            if [[ ! -f "$mobile_file" ]]; then
              echo "⚠️  不完全なセット: $desktop_file (モバイル版が存在しません)"
              incomplete_sets=$((incomplete_sets + 1))
            fi
          done < <(find public/images -name "*-desktop.*")
          
          if [[ $incomplete_sets -gt 0 ]]; then
            echo "⚠️  ${incomplete_sets} 個の不完全なレスポンシブ画像セットが見つかりました"
          else
            echo "✅ レスポンシブ画像セット: OK"
          fi
      
      - name: Check image optimization
        run: |
          # 画像サイズをチェック(警告レベル)
          echo "🖼️  画像サイズをチェック中..."
          
          large_files=0
          while read -r image_file; do
            size=$(stat -c%s "$image_file" 2>/dev/null || stat -f%z "$image_file" 2>/dev/null)
            size_kb=$((size / 1024))
            
            if [[ $size_kb -gt 500 ]]; then
              echo "⚠️  大きなファイル: $image_file (${size_kb}KB)"
              large_files=$((large_files + 1))
            fi
          done < <(find public/images -name "*.jpg" -o -name "*.png")
          
          if [[ $large_files -gt 0 ]]; then
            echo "⚠️  ${large_files} 個の大きな画像ファイルが見つかりました(最適化を検討してください)"
          else
            echo "✅ 画像サイズ: OK"
          fi
          
          echo "📊 チェック完了"

この構成を採用する効果

開発効率の向上

  • 画像の発見が容易:どこに何があるかが明確
  • 命名の統一性:予測可能なファイル名
  • 型安全性:TypeScriptで画像パスの補完
  • リファクタリング安全性:影響範囲の特定が容易

チーム開発での効果

  • 属人化の防止:誰でも画像の場所が分かる
  • コードレビューの質向上:画像追加の意図が明確
  • 新規参加者のオンボーディング:構造が理解しやすい

長期運用での効果

  • 技術負債の蓄積防止:ルールが明確で破綻しにくい
  • メンテナンス性:未使用ファイルの特定が容易
  • パフォーマンス向上:不要な画像の削除が可能

段階的な移行方法

既存プロジェクトへの適用も可能です🔄

Phase 1: 現状把握

# 既存画像の棚卸し
find public -name "*.jpg" -o -name "*.png" -o -name "*.svg" | sort > current-images.txt

# 使用箇所の調査  
grep -r "\.jpg\|\.png\|\.svg" src/ > image-usage.txt

Phase 2: 分類とリネーム

# 新しい構造でディレクトリ作成
mkdir -p public/images/{global/{logos,icons,backgrounds},components/{header,hero,cards,buttons},pages/{home,about,contact}}

# 段階的な移行(例:ロゴから開始)
mv public/logo.svg public/images/global/logos/logo.svg

# ヒーロー画像の移行
mv public/hero-desktop.jpg public/images/components/hero/hero-desktop.jpg
mv public/hero-mobile.jpg public/images/components/hero/hero-mobile.jpg

Phase 3: 型定義の追加

// 少しずつ型定義を拡張
export const ImagePaths = {
  global: {
    logos: {
      main: '/images/global/logos/logo.svg',
      // 他も段階的に追加
    },
  },
  components: {
    header: {
      logo: '/images/components/header/logo-header.svg',
      menuIcon: '/images/components/header/menu-icon.svg',
    },
    hero: {
      desktop: '/images/components/hero/hero-desktop.jpg',
      mobile: '/images/components/hero/hero-mobile.jpg',
    },
    // 他のコンポーネントも順次追加
  },
  // 他のセクションも順次追加
} as const

おわりに

画像管理は地味ですが、プロジェクトの持続可能性に大きく影響する重要な要素です🌱

コンポーネント設計の概念を援用することで、責任が明確で保守しやすい画像管理を実現できます!

特に:

  • チーム開発での混乱を防止
  • 長期運用での技術負債を回避
  • リファクタリング時の安全性向上

これらの効果は、開発速度と品質の両方に好影響をもたらします✨

「傷が深くなる前に」今のうちから、画像管理の仕組みを整えてみませんか💪?

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?